/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

/***********************************************************************
 *
 * FILENAME:
 * gtk_gpsmap.c
 *
 * PURPOSE:
 *	Provide a GPS map interface to the user.
 *
 * LAST MODIFIED:
 *
 ***********************************************************************/

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <gdk/gdkkeysyms.h>

#include "gtkMap.h"

#include "nodeIconManager.h"
#include "windManager.h"
#include "targetLayer.h"
#include "fpManager.h"
#include "distMeasure.h"
#include "fbManager.h"
#include "radarLayer.h"
#include "radarSimLayer.h"
#include "nwsWarnings.h"  
#include "networkLayer.h"
#include "vcConfig.h"  
#include "color.h"  

#ifdef USING_L2_RADAR
  #include "level2Layer.h"
#endif

#undef ENABLE_DEBUG 

extern GuiStruct    guiStr;

//------------- URI Format --------------//
#define URI_MARKER_X    "#X"
#define URI_MARKER_Y    "#Y"
#define URI_MARKER_Z    "#Z"
#define URI_MARKER_S    "#S"
#define URI_MARKER_Q    "#Q"
#define URI_MARKER_Q0   "#O"
#define URI_MARKER_YS   "#U"
#define URI_MARKER_R    "#R"
#define URI_MARKER_W    "#W"
#define URI_MARKER_H    "#H"
#define URI_MARKER_LAT  "#LAT"
#define URI_MARKER_LON  "#LON"

#define URI_HAS_X    (1 << 0)
#define URI_HAS_Y    (1 << 1)
#define URI_HAS_Z    (1 << 2)
#define URI_HAS_S    (1 << 3)
#define URI_HAS_Q    (1 << 4)
#define URI_HAS_Q0   (1 << 5)
#define URI_HAS_YS   (1 << 6)
#define URI_HAS_R    (1 << 7)
#define URI_HAS_W    (1 << 8)
#define URI_HAS_H    (1 << 9)
#define URI_HAS_LAT  (1 << 10)
#define URI_HAS_LON  (1 << 11)
//....
#define URI_FLAG_END (1 << 12)


//------------- MENUS --------------//
static GtkActionEntry map_entries[] = 
{
	{ "FileMenuAction",     NULL, "_Map File" }, /* name, stock id, label */
	{ "BoundaryMenuAction", NULL, "_Boundary Files" }, /* name, stock id, label */
	{ "OSMMenuAction",      NULL, "_OSM Map Selection" }, /* name, stock id, label */
	{ "ZoomMenuAction",     NULL, "_Zoom" }, /* name, stock id, label */
	{ "WPMenuAction",       NULL, "_Flight Plans" }, /* name, stock id, label */
	{ "LayerMenuAction",    NULL, "_Layer Options" }, /* name, stock id, label */
	{ "DistanceMenuAction", NULL, "_Distance Measure" }, /* name, stock id, label */
	{ "UnitMenuAction",     NULL, "Map _Units" }, /* name, stock id, label */

	//------------- File Menu --------------------//
	{ "OpenMap", GTK_STOCK_OPEN,     /* name, stock id */
		"_Open Map", NULL,            /* label, accelerator */
		"Open a GEO Referenced map file",  /* tooltip */ 
		G_CALLBACK(GPSMap::load_image_cb) },

	{ "ClearMaps", GTK_STOCK_DELETE,   /* name, stock id */
		"_Clear Maps", NULL,            /* label, accelerator */
		"Clear loaded maps",  /* tooltip */ 
		G_CALLBACK(GPSMap::clear_maps_cb) },

	//------------- Boundary Menu --------------------//
	{ "LoadFB", GTK_STOCK_OPEN,     // name, stock id 
		"Load _Boundary", NULL,            // label, accelerator 
		"Load a boundary plan from a file",  // tooltip 
		G_CALLBACK(GPSMap::load_fb_cb) },

	{ "ClearFB", GTK_STOCK_CLOSE,     // name, stock id 
		"Clear Boun_dary", NULL,            // label, accelerator 
		"Clear boundary plans from display",  // tooltip 
		G_CALLBACK(GPSMap::clear_fb_cb) },

	//------------- Flight Plan Menu --------------------//
	{ "LoadWP", GTK_STOCK_OPEN,     /* name, stock id */
		"Load _Plan", NULL,            /* label, accelerator */
		"Load a flight plan from a file",  /* tooltip */ 
		G_CALLBACK(GPSMap::load_wp_cb) },

	{ "CreateDTOPlan", GTK_STOCK_HOME,     /* name, stock id */
		"Create _TO Plan", NULL,            /* label, accelerator */
		"Create the default take-off plan",  /* tooltip */ 
		G_CALLBACK(GPSMap::create_dto_cb) },

	{ "CreateDLCPlan", GTK_STOCK_HELP,     /* name, stock id */
		"Create _Lost-Comm Plan", NULL,            /* label, accelerator */
		"Create the default Lost-Comm plan",  /* tooltip */ 
		G_CALLBACK(GPSMap::create_dlc_cb) },

	{ "ClearLocalPlans", GTK_STOCK_CANCEL,     /* name, stock id */
		"_Clear Local Plans", NULL,            /* label, accelerator */
		"Clear all local flight plans",  /* tooltip */ 
		G_CALLBACK(GPSMap::clear_wp_cb) },

	//------------- Zoom Menu --------------------//
	{ "ZoomIn", GTK_STOCK_ZOOM_IN,     /* name, stock id */
		"Zoom _In", NULL,            /* label, accelerator */
		"Zoom in the gps map view",  /* tooltip */ 
		G_CALLBACK(GPSMap::zoom_in_cb) },

	{ "ZoomOut", GTK_STOCK_ZOOM_OUT,     /* name, stock id */
		"Zoom _Out", NULL,            /* label, accelerator */
		"Zoom out the gps map view",  /* tooltip */ 
		G_CALLBACK(GPSMap::zoom_out_cb) },

	{ "ZoomFit", GTK_STOCK_ZOOM_FIT,     /* name, stock id */
		"Zoom _Fit", NULL,            /* label, accelerator */
		"Fit all contents to window view",  /* tooltip */ 
		G_CALLBACK(GPSMap::zoom_fit_cb) },

	{ "ZoomReset", GTK_STOCK_ZOOM_100,     /* name, stock id */
		"Zoom _Reset", NULL,            /* label, accelerator */
		"Reset zoom level of gps map view",  /* tooltip */ 
		G_CALLBACK(GPSMap::zoom_reset_cb) },

	//------------- Map Tools --------------------//
	{ "MapSearch", GTK_STOCK_FIND,     /* name, stock id */
		"_Search Map", NULL,            /* label, accelerator */
		"Search the web for a location to zoom in on the map",  /* tooltip */ 
		G_CALLBACK(GPSMap::map_search_cb) },

	{ "ClearDist", GTK_STOCK_CLOSE,     /* name, stock id */
		"_Clear Distances", NULL,            /* label, accelerator */
		"Clear the distance measurement lines",
		G_CALLBACK(GPSMap::clear_dist_cb) },
};

static GtkToggleActionEntry map_toggle_entries[] = 
{
	//------------- Layers Menu --------------------//
	{ "ToggleMapAnimation", GTK_STOCK_EXECUTE,     // name, stock id
		"Map _Animation", NULL,      // label, accelerator
		"Toggle map animation on/off",   // tooltip
		G_CALLBACK(GPSMap::toggle_animation),
		1 // initial state
	},

	//------------- Waypoint Plans --------------------//
	{ "CreateWP", GTK_STOCK_ADD,     // name, stock id 
		"C_reate New Plan", NULL,     // label, accelerator 
		"Create a new waypoint plan", // tooltip 
		G_CALLBACK(GPSMap::create_wp_cb), 
		0 
	},

	{ "DistanceMeasure", GTK_STOCK_REMOVE, // name, stock id 
		"_Distance Measure", NULL,           // label, accelerator 
		"Measure distances on the map",      // tooltip 
		G_CALLBACK(GPSMap::map_dist_cb),
	  0
	},

};

static const GtkRadioActionEntry units_radio_entries[] = {
	//-- Units
	{ "SI_M_U",            NULL, "meters, m/s",            NULL, NULL, SI_M_U},
	{ "SI_KM_U",           NULL, "kilometers, kph",        NULL, NULL, SI_KM_U},
	{ "ENGLISH_F_U",       NULL, "feet, fps",              NULL, NULL, ENGLISH_F_U},
	{ "ENGLISH_M_U",       NULL, "miles, mph",             NULL, NULL, ENGLISH_M_U},
	{ "ENGLISH_NM_U",      NULL, "nautical miles, knots",  NULL, NULL, ENGLISH_NM_U},
};


//------------------------------------------------------------------
// some utility functions
//------------------------------------------------------------------
double deg2rad(double deg)
{
	return (deg * M_PI / 180.0);
}

double rad2deg(double rad)
{
	return (rad / M_PI * 180.0);
}

double lat2pixel(  double zoom, double lat)
{
	double lat_m;
	double pixel_y;

	lat_m = atanh(sin(lat));
	pixel_y = ( -( (lat_m * TILESIZE * exp(zoom * M_LN2) ) / (2.0*M_PI)) + (exp(zoom * M_LN2) * (TILESIZE/2.0) ) );
	return pixel_y;
}

double pixel2lat(  double zoom, double pixel_y)
{
	double lat, lat_m;

	lat_m = ( (-( pixel_y - ( exp(zoom * M_LN2) * (TILESIZE/2.0) ) ) * (2.0*M_PI)) / (TILESIZE * exp(zoom * M_LN2)) );
	lat = asin(tanh(lat_m));
	return lat;
}

double lon2pixel(  double zoom, double lon)
{
	double pixel_x;
	pixel_x = ( ( (lon * TILESIZE * exp(zoom * M_LN2) ) / (2.0*M_PI) ) + ( exp(zoom * M_LN2) * (TILESIZE/2.0) ) );
	return pixel_x;
}

double pixel2lon(  double zoom, double pixel_x)
{
	double lon;
	lon = ( ((pixel_x - ( exp(zoom * M_LN2) * (TILESIZE/2.0) ) ) *2.0*M_PI) / (TILESIZE * exp(zoom * M_LN2) ) );
	return lon;
}

#define GOOGLE_API_KEY "ABQIAAAAGEPxfABIpmT7pW_lEnm_LhSvrS-hxi5uWvQsolRTszbadZW70BScp7YocWxH0GeVqWU6lvUrEZTw0w"

#define MAP_SOURCE_OSM_MAPNIK               "http://tile.openstreetmap.org/#Z/#X/#Y.png"
#define MAP_SOURCE_OSM_RENDERER             "http://tah.openstreetmap.org/Tiles/tile/#Z/#X/#Y.png"
#define MAP_SOURCE_OSM_CYCLE                "http://andy.sandbox.cloudmade.com/tiles/cycle/#Z/#X/#Y.png"
#define MAP_SOURCE_OSM_MAPLINT              "http://tah.openstreetmap.org/Tiles/maplint/#Z/#X/#Y.png"
#define MAP_SOURCE_OPENAERIALMAP            "http://tile.openaerialmap.org/tiles/1.0.0/openaerialmap-900913/#Z/#X/#Y.jpg"
#define MAP_SOURCE_GOOGLE_MAPS              "http://mt#R.google.com/vt/v=ap.97&x=#X&y=#Y&z=#Z"
#define MAP_SOURCE_GOOGLE_TOPO              "http://mt#R.google.com/mt/v=app.87&x=#X&y=#Y&z=#Z"
#define MAP_SOURCE_GOOGLE_HYBRID            "http://mt#R.google.com/mt/v=w2t.92&hl=en&x=#X&s=&y=#Y&z=#Z"
#define MAP_SOURCE_GOOGLE_SATELLITE         "http://khm#R.google.com/kh/v=39&x=#X&y=#Y&z=#Z"
#define MAP_SOURCE_GOOGLE_SATELLITE_QUAD    "http://khm#R.google.com/kh/v=39&t=#Q"
#define MAP_SOURCE_GOOGLE_DYNAMIC           "http://maps.google.com/staticmap?maptype=roadmap&center=#LAT,#LON&zoom=#Z&size=#Wx#H&sensor=false&key="GOOGLE_API_KEY
#define MAP_SOURCE_GOOGLE_DYNAMIC_MOBILE    "http://maps.google.com/staticmap?maptype=mobile&center=#LAT,#LON&zoom=#Z&size=#Wx#H&sensor=false&key="GOOGLE_API_KEY
#define MAP_SOURCE_GOOGLE_DYNAMIC_SAT       "http://maps.google.com/staticmap?maptype=satellite&center=#LAT,#LON&zoom=#Z&size=#Wx#H&sensor=false&key="GOOGLE_API_KEY
#define MAP_SOURCE_GOOGLE_DYNAMIC_TERRAIN   "http://maps.google.com/staticmap?maptype=terrain&center=#LAT,#LON&zoom=#Z&size=#Wx#H&sensor=false&key="GOOGLE_API_KEY
#define MAP_SOURCE_GOOGLE_DYNAMIC_HYBRID    "http://maps.google.com/staticmap?maptype=hybrid&center=#LAT,#LON&zoom=#Z&size=#Wx#H&sensor=false&key="GOOGLE_API_KEY
#define MAP_SOURCE_MAPS_FOR_FREE            "http://maps-for-free.com/layer/relief/z#Z/row#Y/#Z_#X-#Y.jpg"
#define MAP_SOURCE_VIRTUAL_EARTH            "http://t#R.tiles.virtualearth.net/tiles/r#O.png?g=266&mkt=en-us&shading=hill"
#define MAP_SOURCE_VIRTUAL_EARTH_HYBRID     "http://a#R.ortho.tiles.virtualearth.net/tiles/h#O.jpeg?g=266&mkt=en-us"
#define MAP_SOURCE_VIRTUAL_EARTH_SATELLITE  "http://a#R.ortho.tiles.virtualearth.net/tiles/a#O.jpeg?g=266"
#define MAP_SOURCE_RUNWAYFINDER             "http://www.runwayfinder.com/charts/x#Xy#Yz#S.jpg"

static const map_source_t map_sources[] = {
   {OSM_MAPNIK,              "OSM-Mapnik",              MAP_SOURCE_OSM_MAPNIK,               0, 18, "png" },
   {OSM_RENDERER,            "OSM-Osmarender",          MAP_SOURCE_OSM_RENDERER,             0, 17, "png" },
   {OSM_CYCLE,               "OSM-Cycle",               MAP_SOURCE_OSM_CYCLE,                0, 18, "png" },
   {OSM_MAPLINT,             "OSM-Maplint",             MAP_SOURCE_OSM_MAPLINT,             12, 16, "png"},
   {OPENAERIALMAP,           "OpenAerialMap",           MAP_SOURCE_OPENAERIALMAP,            0, 20, "png"},
   {GOOGLE_MAPS,             "Google-Maps",             MAP_SOURCE_GOOGLE_MAPS,              0, 19, "png"},
   {GOOGLE_TOPO,             "Google-Topo",             MAP_SOURCE_GOOGLE_TOPO,              0, 15, "png"},
   {GOOGLE_HYBRID,           "Google-Hybrid",           MAP_SOURCE_GOOGLE_HYBRID,            0, 19, "png"},
   {GOOGLE_SATELLITE,        "Google-Satellite",        MAP_SOURCE_GOOGLE_SATELLITE,         0, 19, "png"},
   {GOOGLE_SATELLITE_QUAD,   "Google-Satellite-Quad",   MAP_SOURCE_GOOGLE_SATELLITE_QUAD,    0, 19, "png"},
   {GOOGLE_DYNAMIC,          "Google-DM-Roadmap",       MAP_SOURCE_GOOGLE_DYNAMIC,           0, 19, "gif"},
   {GOOGLE_DYNAMIC_MOBILE,   "Google-DM-Mobile",        MAP_SOURCE_GOOGLE_DYNAMIC_MOBILE,    0, 19, "gif"},
   {GOOGLE_DYNAMIC_SAT,      "Google-DM-Satellite",     MAP_SOURCE_GOOGLE_DYNAMIC_SAT,       0, 19, "gif"},
   {GOOGLE_DYNAMIC_TERRAIN,  "Google-DM-Terrain",       MAP_SOURCE_GOOGLE_DYNAMIC_TERRAIN,   0, 19, "gif"},
   {GOOGLE_DYNAMIC_HYBRID,   "Google-DM-Hybrid",        MAP_SOURCE_GOOGLE_DYNAMIC_HYBRID,    0, 19, "gif"},
   {MAPS_FOR_FREE,           "Maps-For-Free",           MAP_SOURCE_MAPS_FOR_FREE,            0, 10, "jpg"},
   {VIRTUAL_EARTH,           "Virtual-Earth",           MAP_SOURCE_VIRTUAL_EARTH,            0, 19, "png"},
   {VIRTUAL_EARTH_HYBRID,    "Virtual-Earth-Hybrid",    MAP_SOURCE_VIRTUAL_EARTH_HYBRID,     0, 19, "jpeg"},
   {VIRTUAL_EARTH_SATELLITE, "Virtual-Earth-Satellite", MAP_SOURCE_VIRTUAL_EARTH_SATELLITE,  0, 19, "jpeg"},
   {RUNWAYFINDER,            "Runway Finder",           MAP_SOURCE_RUNWAYFINDER,             0, 17, "jpg"},
};

/* Radio items */
static const GtkRadioActionEntry map_radio_entries[] = {
	//-- Maps
	{ "OSM_MAPNIK",              NULL, map_sources[OSM_MAPNIK].name,              NULL, NULL, OSM_MAPNIK},
	{ "OSM_RENDERER",            NULL, map_sources[OSM_RENDERER].name,            NULL, NULL, OSM_RENDERER},
	{ "OSM_CYCLE",               NULL, map_sources[OSM_CYCLE].name,               NULL, NULL, OSM_CYCLE},
	{ "OSM_MAPLINT",             NULL, map_sources[OSM_MAPLINT].name,             NULL, NULL, OSM_MAPLINT},
	{ "OPENAERIALMAP",           NULL, map_sources[OPENAERIALMAP].name,           NULL, NULL, OPENAERIALMAP},
	{ "GOOGLE_MAPS",             NULL, map_sources[GOOGLE_MAPS].name,             NULL, NULL, GOOGLE_MAPS},
	{ "GOOGLE_TOPO",             NULL, map_sources[GOOGLE_TOPO].name,             NULL, NULL, GOOGLE_TOPO},
	{ "GOOGLE_HYBRID",           NULL, map_sources[GOOGLE_HYBRID].name,           NULL, NULL, GOOGLE_HYBRID},
	{ "GOOGLE_SATELLITE",        NULL, map_sources[GOOGLE_SATELLITE].name,        NULL, NULL, GOOGLE_SATELLITE},
	{ "GOOGLE_SATELLITE_QUAD",   NULL, map_sources[GOOGLE_SATELLITE_QUAD].name,   NULL, NULL, GOOGLE_SATELLITE_QUAD},
	{ "GOOGLE_DYNAMIC",          NULL, map_sources[GOOGLE_DYNAMIC].name,          NULL, NULL, GOOGLE_DYNAMIC},
	{ "GOOGLE_DYNAMIC_MOBILE",   NULL, map_sources[GOOGLE_DYNAMIC_MOBILE].name,   NULL, NULL, GOOGLE_DYNAMIC_MOBILE},
	{ "GOOGLE_DYNAMIC_SAT",      NULL, map_sources[GOOGLE_DYNAMIC_SAT].name,      NULL, NULL, GOOGLE_DYNAMIC_SAT},
	{ "GOOGLE_DYNAMIC_TERRAIN",  NULL, map_sources[GOOGLE_DYNAMIC_TERRAIN].name,  NULL, NULL, GOOGLE_DYNAMIC_TERRAIN},
	{ "GOOGLE_DYNAMIC_HYBRID",   NULL, map_sources[GOOGLE_DYNAMIC_HYBRID].name,   NULL, NULL, GOOGLE_DYNAMIC_HYBRID},
	{ "MAPS_FOR_FREE",           NULL, map_sources[MAPS_FOR_FREE].name,           NULL, NULL, MAPS_FOR_FREE},
	{ "VIRTUAL_EARTH",           NULL, map_sources[VIRTUAL_EARTH].name,           NULL, NULL, VIRTUAL_EARTH},
	{ "VIRTUAL_EARTH_HYBRID",    NULL, map_sources[VIRTUAL_EARTH_HYBRID].name,    NULL, NULL, VIRTUAL_EARTH_HYBRID},
	{ "VIRTUAL_EARTH_SATELLITE", NULL, map_sources[VIRTUAL_EARTH_SATELLITE].name, NULL, NULL, VIRTUAL_EARTH_SATELLITE},
	{ "RUNWAYFINDER",            NULL, map_sources[RUNWAYFINDER].name,            NULL, NULL, RUNWAYFINDER},
};


//------------------------------------------------------------------
//
//------------------------------------------------------------------

GPSMap::GPSMap()
{
	zoom_level         = 1.0; // implies we are DEFAULT_DEG_PER_PIXEL
	menu_ui_file       = guiStr.config.menu_ui_file;
	scrolled_mode      = MAP_NO_SCROLLBARS; // MAP_AUTO_SCROLLBARS;
	window             = NULL;
	canvas             = NULL;
	mouse_status       = NULL;
	animate_motion     = TRUE;
	animate_zoom       = FALSE;
	creating_plan      = FALSE;

	drag_item          = NULL;
	drag_point_ind     = -1;
	left_mouse_pressed = 0;
	left_mouse_x       = 0; 
	left_mouse_y       = 0;
	last_directory     = NULL;

	// init the layer groups to known state
	for( int i=0; i < NUM_LAYERS; i++) {
		layerG[i]         = NULL;	
		layer_is_shown[i] = true;	
		layer_zooming[i]  = false;
		layer_manager[i]  = NULL;
	}

	// init the geo maps to known state
	for( int i=0; i < MAX_NUM_GEOIMG; i++) {
		geoMap[i].geoImg   = NULL;
		geoMap[i].geoVideo = NULL;
		//geoMap[i].is_shown = false;
	}

	osm_map_layer = NULL;

	mapSource  = DEFAULT_MAP;
	zoom_level = 1.0;
	map_zoom   = 1;
	map_x = 0;
	map_y = 0;

}

void GPSMap::clear_geoImage()
{
	//cout << "GPSMap::clear_geoImage()" << endl;
	for(int i=0; i < MAX_NUM_GEOIMG; i++) {
		if( geoMap[i].geoImg ) {
			geoMap[i].geoImg->Close();
			delete geoMap[i].geoImg;
		}
		// remove img_item
		if( geoMap[i].img_item ) {
			layer_list.delLayer( geoMap[i].img_item );
			goo_canvas_item_remove(  GOO_CANVAS_ITEM(geoMap[i].img_item) );
		}
		geoMap[i].geoImg   = NULL;
		geoMap[i].img_item = NULL;
		//geoMap[i].is_shown = false;
	}
	mapCount = 0;
}

GPSMap::~GPSMap()
{
	for(int i=0; i < MAX_NUM_GEOIMG; i++) {
		if( geoMap[i].geoImg ) {
			geoMap[i].geoImg->Close();
			delete geoMap[i].geoImg;
		}
		geoMap[i].geoImg   = NULL;
		//geoMap[i].is_shown = false;
	}

	for(int i=0; i < NUM_LAYERS; i++) {
		if( layer_manager[i] )
			delete layer_manager[i];
		layerG[i]         = NULL;	
		layer_is_shown[i] = false;	
		layer_zooming[i]  = false;
		layer_manager[i]  = NULL;
	}

	if( cache_dir )
		g_free(cache_dir);
	cache_dir = NULL;

	if( soup_session ) {
		soup_session_abort(soup_session);
		g_object_unref(soup_session);
	}
	soup_session = NULL;

	if( tile_queue )
		g_hash_table_destroy(tile_queue);
	tile_queue = NULL;
	if( missing_tiles )  
		g_hash_table_destroy(missing_tiles);
	missing_tiles = NULL;
}

bool GPSMap::Init(GtkWidget *parent)
{ 
	//-- make sure we have a parent window --//
	if( parent == NULL ) 
		return false;
	window = parent;

#ifdef USE_GDK_THREAD
	// GTK/GDK thread enter
	gdk_threads_enter();
#endif

	/*
	//  gdk_rgb_init() should be called at main level, but just in case
	gdk_rgb_init();
	*/

	//-- menus --//
	GError *error = NULL;

	// Create new GtkActionGroup and add entries 
	action_group = gtk_action_group_new ("MapActions");
	gtk_action_group_add_actions (action_group, map_entries,  G_N_ELEMENTS(map_entries), this);
	gtk_action_group_add_toggle_actions (action_group, map_toggle_entries, G_N_ELEMENTS(map_toggle_entries), this);
	gtk_action_group_add_radio_actions (action_group, map_radio_entries, G_N_ELEMENTS(map_radio_entries), 
	                                    mapSource, G_CALLBACK(osm_mapserver_cb), this);
	gtk_action_group_add_radio_actions (action_group, units_radio_entries, G_N_ELEMENTS(units_radio_entries), 
	                                    guiStr.config.units, G_CALLBACK(units_cb), this);

	// Create new GtkUIManager and insert action group 
	menu_manager = gtk_ui_manager_new ();
	gtk_ui_manager_insert_action_group (menu_manager, action_group, 0);

	// Load UI XML into ui manager 
	gtk_ui_manager_add_ui_from_file (menu_manager, menu_ui_file.c_str(), &error);
	if (error) {
		printf ("GPSMap::Init - building menus failed: %s\n\tFile -> %s\n", error->message, menu_ui_file.c_str() );
		g_error_free (error);
		return false;
	}

	//-- goo canvas --//
	canvas = GOO_CANVAS( goo_canvas_new() );
	//GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS);
	g_object_set(G_OBJECT (canvas),
			"automatic-bounds", TRUE,
			"bounds-from-origin", TRUE,
			"bounds-padding", 0.0,
			"redraw-when-scrolled", FALSE,
			"background-color-rgb", 0xF1EEE8,
			"can-focus", TRUE,
			NULL);

	root_item    = goo_canvas_get_root_item (GOO_CANVAS (canvas));
	movable_root = goo_canvas_group_new (root_item,"can-focus", TRUE, NULL);

	gtk_widget_show (GTK_WIDGET(canvas));

	// create layer managers
	layer_manager[ICON_LAYER]     = new NodeIconManager;
	layer_manager[WIND_LAYER]     = new WindManager;
	layer_manager[TARGET_LAYER]   = new TargetManager;
	layer_manager[FB_LAYER]       = new FBManager;
	layer_manager[FP_LAYER]       = new FPManager;
	layer_manager[NWS_WARN_LAYER] = new NWSWarnings;
	layer_manager[RADAR_LAYER]    = new RadarManager;
#ifdef USING_L2_RADAR
	layer_manager[LEVEL2_LAYER]   = new Level2Manager;
#endif
	layer_manager[RADAR_SIM_LAYER]= new RadarSimManager;
	layer_manager[NETWORK_LAYER]  = new NetworkLayerManager;
	layer_manager[DM_LAYER]       = new DMManager;

	// set layer names
	layer_names[OSM_LAYER]      = "Open Source Maps";
	layer_names[MAP_LAYER]      = "GEO-Ref Images";
	layer_names[NWS_WARN_LAYER] = "NWS Warnings";
	layer_names[RADAR_LAYER]    = "Radar";
	layer_names[RADAR_SIM_LAYER]= "Simulated Radar";
	layer_names[LEVEL2_LAYER]   = "Level II Radar";
	layer_names[FB_LAYER]       = "Boundaries";
	layer_names[NETWORK_LAYER]  = "Network Links";
	layer_names[FP_LAYER]       = "Flight Plans";
	layer_names[ICON_LAYER]     = "Node Icons";
	layer_names[WIND_LAYER]     = "Wind Measurements";
	layer_names[DM_LAYER]       = "Distance Measure";
	layer_names[TARGET_LAYER]   = "Targets";

	// layer list
	layer_list.init();

	// create the map layers
	for(int i=0; i < NUM_LAYERS; i++) {
		// create groups 
		if( i < FB_LAYER )
			layerG[i] = GOO_CANVAS_GROUP(goo_canvas_group_new (movable_root, NULL));
		else {
			if( i == FB_LAYER )
				zoom_fit_group = GOO_CANVAS_GROUP(goo_canvas_group_new (movable_root, NULL));
			layerG[i] = GOO_CANVAS_GROUP(goo_canvas_group_new (GOO_CANVAS_ITEM(zoom_fit_group), NULL));
		}
		g_object_set (layerG[i], "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);

		//cout << "Layer: " << layer_names[i] << " layerG=" << layerG[i] << endl;
		
		// add the group layer, we pass the group number so that the layer list
		// knows that it should call me
		addLayer( layer_names[i], (GooCanvasItem*)i );

		// init layer default properties
		toggleLayerShown( (GPSMap::GroupLayers)i, false);
		layer_zooming[i]  = false;

		if( layer_manager[i] ) {
			layer_manager[i]->init(this);
			//if( i != RADAR_LAYER )
				g_timeout_add (500, layer_manager[i]->layer_timeout_cb, layer_manager[i]);
			//else
				//g_timeout_add (5000, layer_manager[i]->layer_timeout_cb, layer_manager[i]);
		}
	}

	// it is convient to have a simple point to the map group
	mapG = layerG[MAP_LAYER];

	// make sure that the mapping layers are zoomable 
	layer_zooming[MAP_LAYER]   = TRUE;
	//layer_zooming[RADAR_LAYER] = TRUE;

/*
	//-- setup scroll window, with autmatic scrollbaras --//
	scrolled_win = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);

	switch( scrolled_mode ) {
	case MAP_SHOW_SCROLLBARS:
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_win),
	GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS);
	break;
	case MAP_AUTO_SCROLLBARS:
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_win),
	GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	break;
	case MAP_NO_SCROLLBARS:
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_win),
	GTK_POLICY_NEVER, GTK_POLICY_NEVER);
	break;
	}

	gtk_widget_show (scrolled_win);

	// add scrolled window to main window
	gtk_container_add (GTK_CONTAINER (window), scrolled_win);
	// add me to the scrolled window
	gtk_container_add (GTK_CONTAINER (scrolled_win), GTK_WIDGET(canvas));
*/

	// expose signal handler
	gtk_signal_connect(GTK_OBJECT(window), 
			"expose_event", 
			GTK_SIGNAL_FUNC (GPSMap::window_expose_event_cb), 
			this);

	// keyboard signal handler
	gtk_signal_connect(GTK_OBJECT(window), 
			"key_press_event", 
			GTK_SIGNAL_FUNC (GPSMap::window_key_event_cb), 
			this);

	//gtk_signal_connect(GTK_OBJECT(window), 
			//"key_release_event", 
			//GTK_SIGNAL_FUNC (GPSMap::window_key_event_cb), 
			//this);

	// mouse button handler
	gtk_signal_connect(GTK_OBJECT(window), 
			"button_press_event", 
			GTK_SIGNAL_FUNC (GPSMap::window_button_event_cb), 
			this);

	gtk_signal_connect(GTK_OBJECT(window), 
			"button_release_event", 
			GTK_SIGNAL_FUNC (GPSMap::window_button_event_cb), 
			this);

	//-- mouse motion handler
	gtk_signal_connect(GTK_OBJECT(window), 
			"motion_notify_event", 
			GTK_SIGNAL_FUNC (GPSMap::window_motion_event_cb), 
			this);

	//-- mouse scroll handler
	g_signal_connect(G_OBJECT(canvas),
			"scroll-event", 
			G_CALLBACK (GPSMap::mouse_scroll_event_cb), 
			this);


	// add canvas to window and show them all
	gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET(canvas));
	gtk_widget_show_all(window);

#ifdef USE_GDK_THREAD
	// GTK/GDK thread leave
	gdk_flush();
	gdk_threads_leave();
#endif

	return true;
}

bool GPSMap::initOSM(MapSource sourceInd)
{
	if( sourceInd > NUM_MAP_SOURCE )
		return false;
	if( sourceInd < NUM_MAP_SOURCE )
		mapSource = sourceInd;

	if( !goo_canvas_item_is_visible( GOO_CANVAS_ITEM(layerG[OSM_LAYER])) )
		return false;

	cout << "Initializing Open Source Mapping: .............. [" GREEN << map_sources[mapSource].name << ATTROFF "]" << endl;

	// clean up any existing sources
	if( cache_dir )
		g_free(cache_dir);
	if( soup_session ) {
		soup_session_abort(soup_session);
		g_object_unref(soup_session);
	}
	if( tile_queue )
		g_hash_table_destroy(tile_queue);
	if( missing_tiles )  
		g_hash_table_destroy(missing_tiles);

	// start new OSM session
	if( mapSource == GOOGLE_DYNAMIC || mapSource == GOOGLE_DYNAMIC_SAT 
	 || mapSource == GOOGLE_DYNAMIC_TERRAIN || mapSource == GOOGLE_DYNAMIC_HYBRID 
	 || mapSource == GOOGLE_DYNAMIC_MOBILE )
		map_auto_cache = false;
	else
		map_auto_cache = true;
	map_auto_download = true;
	cache_dir         = g_strdup_printf("%s/%s", GUI_MAPS_DIR, map_sources[mapSource].name);
	repo_uri          = map_sources[mapSource].uri;

	inspect_map_uri();

	soup_session = soup_session_async_new_with_options(
			SOUP_SESSION_MAX_CONNS_PER_HOST, 10,
			SOUP_SESSION_USER_AGENT,
			"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11",
			NULL);

	//Hash table which maps tile d/l URIs to SoupMessage requests
	tile_queue = g_hash_table_new (g_str_hash, g_str_equal);
	numDownloading = 0;

	//Some mapping providers (Google) have varying degrees of tiles at multiple zoom levels
	missing_tiles = g_hash_table_new (g_str_hash, g_str_equal);

	fill_map_tiles();

	// set the layer as active and shown
	setLayerListShown( (GooCanvasItem*)OSM_LAYER, true );

	// make sure parent manager layer is shown
	toggleLayerShown(OSM_LAYER, true);

	return true;
}

void GPSMap::setup_item(GooCanvasItem *item)
{
	g_signal_connect (G_OBJECT (item), "button-press-event", G_CALLBACK (item_button_press_event_cb), this);
	g_signal_connect (G_OBJECT (item), "button-release-event", G_CALLBACK (item_button_release_event_cb), this);
	g_signal_connect (G_OBJECT (item), "motion-notify-event", G_CALLBACK (item_motion_notify_event_cb), this);
	g_signal_connect (G_OBJECT (item), "key-press-event", G_CALLBACK (item_key_event_cb), this);
	g_signal_connect (G_OBJECT (item), "key-release-event", G_CALLBACK (item_key_event_cb), this);
}
//////////////////////////////////////////////////////
// Add a vidoe image to the map
//////////////////////////////////////////////////////
void GPSMap::update_videoImage(int mapCount, GdkPixbuf *pixbuf)
{
	if( geoMap[mapCount].img_item && pixbuf) {
		g_object_set(G_OBJECT(geoMap[mapCount].img_item), "pixbuf", pixbuf, NULL);
	}
}

int GPSMap::add_videoImage(int cam, GdkPixbuf *pixbuf)
{
	//cout << "GPSMap::add_geoVideo mapCount=" << mapCount << endl;

	char camName[32] ;
	sprintf(camName, "Cam-%i",cam);

	if( mapCount >= MAX_NUM_GEOIMG ) {
		cout << "GPSMap::add_geoImage - maximum number of georeferenced maps are loaded" << endl;
		return -1;
	}

	geoMap[mapCount].geoVideo = new GeoVideo();

	if( geoMap[mapCount].geoVideo == NULL ) {
		cerr << "GPSMap::add_geoVideo -  could not create new GeoVideo() "<< endl;
		return -1;
	}

		// determine scale width/height
		double scale = 0.5;
		geoMap[mapCount].scale_x = scale;
		geoMap[mapCount].scale_y = scale;

		// get coordinates of new maps tie points
		double new_x, new_y;
		switch( cam ) {
			case 0:
				new_x = -1.0;
				new_y = 5;
				break;
			case 1:
				new_x = 2.25;
				new_y = 5;
				break;
			case 2:
				new_x = -1.0;
				new_y = 2.35;
				break;
			case 3:
				new_x = 2.25;
				new_y = 2.35;
				break;
		} 

		geoMap[mapCount].geoVideo->setTiePoints(new_x, new_y);
		geoMap[mapCount].geoVideo->setPixbuf(pixbuf);
		geoMap[mapCount].geoVideo->setDegPerP(degPerXp()*scale, degPerYp(0)*scale);

		// convert geographic to base map image coordinates,
		geoToLayer(MAP_LAYER, new_x, new_y);

		// this is the offset for this map
		geoMap[mapCount].x_off = new_x;
		geoMap[mapCount].y_off = new_y;

		// goocanvas will now have its own copy of the pixbuf
		geoMap[mapCount].img_item = goo_canvas_image_new( GOO_CANVAS_ITEM(mapG), pixbuf, 0.0, 0.0, NULL);

		if( geoMap[mapCount].img_item ) {
			// scale and translate image
			goo_canvas_item_translate(geoMap[mapCount].img_item, geoMap[mapCount].x_off, geoMap[mapCount].y_off);
			goo_canvas_item_scale(geoMap[mapCount].img_item, geoMap[mapCount].scale_x, geoMap[mapCount].scale_y);
			goo_canvas_item_request_update(geoMap[mapCount].img_item);
			//geoMap[mapCount].is_shown = true;

			// add the image to the layer list
			addLayer( camName, geoMap[mapCount].img_item, (GooCanvasItem *)MAP_LAYER );
			setLayerListShown(geoMap[mapCount].img_item, true );

			// make sure the map layer manager is shown
			toggleLayerShown( MAP_LAYER, true);

			return ++mapCount;
		} else
			cout << "GPSMap::add_geoVideo - error getting canvas item from pixbuf" << endl;

	delete geoMap[mapCount].geoVideo;
	geoMap[mapCount].geoVideo = NULL;
	return -1;
}

//////////////////////////////////////////////////////
// Add a geo referenced image to the map
//////////////////////////////////////////////////////
int GPSMap::add_geoImage(String file)
{
	//cout << "GPSMap::add_geoImag mapCount=" << mapCount << endl;

	GdkPixbuf *pixbuf = NULL;

	if( mapCount >= MAX_NUM_GEOIMG ) {
		cout << "GPSMap::add_geoImage - maximum number of georeferenced maps are loaded" << endl;
		return -1;
	}

	geoMap[mapCount].geoImg = new GeoImage();

	if( geoMap[mapCount].geoImg == NULL ) {
		cerr << "GPSMap::add_geoImage -  could not create new GeoRefMap() "<< endl;
		return -1;
	}

	if( geoMap[mapCount].geoImg->Open(file) ) {
		//cout << "adding pixbuf index=" << mapCount << endl;
		// we are only getting the real pointer, not a copy so don't unref
		pixbuf = geoMap[mapCount].geoImg->getPixbuf();

		//geoMap[mapCount].width  = (double) gdk_pixbuf_get_width(pixbuf);
		//geoMap[mapCount].height = (double) gdk_pixbuf_get_height(pixbuf);

		// get coordinates of new maps tie points
		double w2 = geoMap[mapCount].geoImg->getWidth() / 2.0;
		double h2 = geoMap[mapCount].geoImg->getHeight() / 2.0;
		geoMap[mapCount].geoImg->imageToGeo(w2, h2);

		// determine scale width/height
		geoMap[mapCount].scale_x = geoMap[mapCount].geoImg->getDegPerXp() / degPerXp();
		geoMap[mapCount].scale_y = fabs(geoMap[mapCount].geoImg->getDegPerYp() / degPerYp(h2));

		// get coordinates of new maps tie points
		double new_x, new_y;
		geoMap[mapCount].geoImg->getTiePoints(new_x, new_y);

		// convert geographic to base map image coordinates,
		geoToLayer(MAP_LAYER, new_x, new_y);

		// this is the offset for this map
		geoMap[mapCount].x_off = new_x;
		geoMap[mapCount].y_off = new_y;

		// goocanvas will now have its own copy of the pixbuf
		geoMap[mapCount].img_item = goo_canvas_image_new( GOO_CANVAS_ITEM(mapG), pixbuf, 0.0, 0.0, NULL);

		if( geoMap[mapCount].img_item ) {
			// scale and translate image
			goo_canvas_item_translate(geoMap[mapCount].img_item, geoMap[mapCount].x_off, geoMap[mapCount].y_off);
			goo_canvas_item_scale(geoMap[mapCount].img_item, geoMap[mapCount].scale_x, geoMap[mapCount].scale_y);

			goo_canvas_item_request_update(geoMap[mapCount].img_item);
			//geoMap[mapCount].is_shown = true;

			// add the image to the layer list
			addLayer( file, geoMap[mapCount].img_item, (GooCanvasItem *)MAP_LAYER );
			setLayerListShown(geoMap[mapCount].img_item, true );

			// make sure the map layer manager is shown
			toggleLayerShown( MAP_LAYER, true);

			return ++mapCount;
		} else
			cout << "GPSMap::add_geoImage - error getting canvas item from pixbuf" << endl;
	} 

	delete geoMap[mapCount].geoImg;
	geoMap[mapCount].geoImg = NULL;
	return -1;
}

String GPSMap::getRasterFile(int imgInd)
{
	if(imgInd >= 0 && imgInd < mapCount)
		if( geoMap[imgInd].geoImg )
			return geoMap[imgInd].geoImg->getFile();
	return "";
}


int GPSMap::mouse_scroll_event_cb(GtkWidget *widget, GdkEventScroll  *event, GPSMap* me)
{
	//cout << "GPSMap::mouse_scroll_event_cb" << endl;

	if( !me )
		return FALSE;

	// window location of mouse during scroll
	double x = (event->x);
	double y = (event->y);

	me->windowToGeo(x,y);

	/* determine if the scroll wheel was rolled up or down */ 
	switch (((GdkEventScroll*)event)->direction){
		case GDK_SCROLL_DOWN:
			/* increase the size of the displayed image */
			me->zoomOutGeo(x,y,1);
			break;
		case GDK_SCROLL_UP:
			/* decrease the size of the displayed image */
			me->zoomInGeo(x,y,1);
			break;
		case GDK_SCROLL_LEFT: case GDK_SCROLL_RIGHT:
			//printf("gtk_gpsmap_mouse_scroll: left and right are not functional\n");
			break;
	}
	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return TRUE;
}

int GPSMap::window_motion_event_cb(GtkWidget *widget, GdkEventMotion *event, GPSMap* me)
{
	double imgx, imgy;

	if( !me ) return FALSE;

	//cout << "GPSMap::window_motion_event_cb" << endl;
	gtk_widget_grab_focus(widget);
	
	imgx = event->x; 
	imgy = event->y;

	//-- convert window coordinates to lat, lon 
	if(me->mouse_status) {
		me->windowToGeo(imgx, imgy);

		Client * me_client = guiStr.shm_ptr->clients.getClient(0);
		double range = NAN;
		if( me_client ) {
			GPSLLA_t gps; 
			gps.lon = imgx; 
			gps.lat = imgy; 
			gps.alt = me_client->telemetry.gps.pos.alt;
			range = me_client->rangeTo(gps);

			range = meterToLengthU(range, guiStr.config.units);
		}

		// make readable string for user 
		char mouse_info[256];
		int n=0;
		//n += sprintf(mouse_info + n,"Mouse: [%i, %i]", x, y);
		//n += sprintf(mouse_info + n," || [%.7f, %.7f]", imgx, imgy);
		//n += sprintf(mouse_info + n," || %s", me->geoMap[0].geoImg->getMouseStr(imgx,imgy).c_str() );
		
		n += sprintf(mouse_info + n,"GEO: [%.7f, %.7f]", imgx, imgy);
		if( !IsNaN(range) ) {
			n += sprintf(mouse_info + n," || Range: %.1f %s", range, lengthU_str(guiStr.config.units, range) );
			gtk_statusbar_push(me->mouse_status, 0, mouse_info);
		}
	}

	// reset values, since they were converted to lat, lon in [deg]
	imgx = event->x; 
	imgy = event->y;

	if( me->left_mouse_pressed && !me->drag_item 
			&& (event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK ) 
	{
		// change the cursor
		GdkCursor* cursor;
		cursor = gdk_cursor_new(GDK_FLEUR);
		gdk_window_set_cursor(widget->window, cursor);
		gdk_cursor_destroy(cursor);

		//printf("window_motion_event_cb: event->type=%i e->x=%lf e->y=%lf imgx=%lf imgy=%lf\n", 
					//event->type, event->x, event->y, imgx, imgy);
		/*
			GtkAdjustment* hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(me->scrolled_win));
			GtkAdjustment* vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(me->scrolled_win));
			double h_val = gtk_adjustment_get_value(hadj);
			double v_val = gtk_adjustment_get_value(vadj);
		*/

		// turn off tracking of any nodes
		NodeIconManager *iconM = (NodeIconManager *)me->layer_manager[ICON_LAYER];
		if( iconM && iconM->isTracking() ) iconM->clearAllTrack();

		double dx = imgx - me->left_mouse_x;
		double dy = imgy - me->left_mouse_y;

		switch( me->scrolled_mode ) {
			case MAP_SHOW_SCROLLBARS: case MAP_AUTO_SCROLLBARS:
				//printf("window_scroll: h_val=%f v_val=%f new h=%f new v=%f\n",
				//h_val, v_val, h_val - (imgx - me->left_mouse_x), v_val - (imgy - me->left_mouse_y)); 
				/*
					gtk_adjustment_set_value(hadj, h_val - (imgx - me->left_mouse_x) );
					gtk_adjustment_set_value(vadj, v_val - (imgy - me->left_mouse_y) );

					gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(me->scrolled_win), hadj);
					gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(me->scrolled_win), vadj);
					*/				
				break;
			case MAP_NO_SCROLLBARS:
				// Translate the entire world
				goo_canvas_item_translate(GOO_CANVAS_ITEM(me->movable_root), dx, dy);
				break;
		}

		me->left_mouse_x = imgx;
		me->left_mouse_y = imgy;
		return TRUE;
	}


	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return FALSE;
}

int GPSMap::window_expose_event_cb(GtkWidget *widget, GdkEventExpose *event, GPSMap *map)
{
	//cout << "GPSMap::window_expose_event_cb -- " 
		//<< " width=" << map->getWidth()
		//<< " height=" << map->getHeight() << endl;

	// update tiles
	if(map) { 
		map->fill_map_tiles();
		map->setup_scale();
		//return TRUE;
		return false;
	}

	// Returning FALSE propagates the event to parent items;
	return false;
}

int GPSMap::window_key_event_cb(GtkWidget *widget, GdkEventKey *event, GPSMap* me )
{
	//printf("GPSMap::window_key_event_cb: event->type = %i event->button=%i str=%s\n", 
		//event->type, event->keyval, event->string);

	RadarManager *radar = (RadarManager *)me->layer_manager[RADAR_LAYER];

	switch( event->keyval ) {
		//-- zoom key interface
		case 'f': case 'F':
			me->zoomFit();
			break;
		case '0':
			me->zoomReset();
			break;
		case '-':
			me->zoomOutWin();
			break;
		case '+':
		case '=':
			me->zoomInWin();
			break;

		//-- radar key interface
		case 's':
			if( radar ) radar->toggle_running();
			break;

		case 'S':
			if( radar ) radar->minT_step(0);
			break;

		case GDK_Left:
			if( radar ) {
				if( (event->state & GDK_SHIFT_MASK) ) 
					radar->minT_step(-1);
				else
					radar->step(-1);
			}
			break;

		case GDK_Right:
			if( radar ) {
				if( (event->state & GDK_SHIFT_MASK) ) 
					radar->minT_step(1);
				else
					radar->step(1);
			}
			break;

		case GDK_Up:
			if( radar ) radar->step_runRate(1);
			break;

		case GDK_Down:
			if( radar ) radar->step_runRate(-1);
			break;

		//-- layer key interface
		case GDK_F1:
		case GDK_F2:
		case GDK_F3:
		case GDK_F4:
		case GDK_F5:
		case GDK_F6:
		case GDK_F7:
		case GDK_F8:
		case GDK_F9:
		case GDK_F10:
		case GDK_F11:
		case GDK_F12:
			me->toggleLayerShown( (GPSMap::GroupLayers)(event->keyval - GDK_F1 + 1) );
			break;

		default:
			//cout << "Keyval: " <<  event->keyval << endl;
			return FALSE; // propogate event 
	}

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return TRUE;
}

int GPSMap::window_button_event_cb(GtkWidget *widget, GdkEventButton *event, GPSMap* me)
{
	//cout << "GPSMap::window_button_event_cb" << endl;
	if( me == NULL )
		return FALSE;
	int button = event->button;

	// convert window to world coordinates
	double imgx, imgy;
	imgx = event->x;
	imgy = event->y;

	//cout << "window_button_press: event->type = " << event->type << endl;
	switch( button ) {
		case LEFT_MOUSE: 
			switch (event->type) {
				case GDK_BUTTON_PRESS: 
					{
						if( me->creating_plan ) {
							FPManager *fpM = (FPManager *)me->layer_manager[FP_LAYER];
							if( !fpM ) return false;

							me->windowToGeo(imgx,imgy);

							// fill in waypoint values
							GPSWaypoint_t point;
							point.Lon = imgx;
							point.Lat = imgy;
							point.index = fpM->numPoints(MAX_CLIENTS);
							point.Next  = point.index + 1;

							// see if this point should be an orbit point
							if( (event->state & GDK_CONTROL_MASK) ) {
								point.OrbitRadius    = 100; // make 100 m orbit default
								point.OrbitDirection = 0; // make CW orbit direction
							}

							if( fpM->add_waypoint(MAX_CLIENTS, point) ) {
								fpM->clear_client_canvas(MAX_CLIENTS);
								fpM->create_canvas_flightplan(MAX_CLIENTS);
							}

							return FALSE;
						} else if( me->creating_dist ) {
							//cout << "button: creating_dist=" << me->creating_dist << endl;
							DMManager *dmM = (DMManager *)me->layer_manager[DM_LAYER];
							if( !dmM ) return false;

							me->windowToGeo(imgx,imgy);

							// fill in waypoint values
							GPSWaypoint_t point;
							point.Lon = imgx;
							point.Lat = imgy;
							point.index = dmM->numPoints();
							point.Next  = point.index + 1;

							// see if this point should be an orbit point
							if( (event->state & GDK_CONTROL_MASK) ) {
								point.OrbitRadius    = 100; // make 100 m orbit default
								point.OrbitDirection = 0; // make CW orbit direction
							}

							if( dmM->add_waypoint(point) ) {
								dmM->clear_client_canvas();
								dmM->create_canvas_flightplan();
							}

							return FALSE;
						}
						//goo_canvas_window_to_world(GNOME_CANVAS(me->canvas), imgx, imgy, &imgx, &imgy);
						me->left_mouse_pressed = 1;
						me->left_mouse_x = event->x;
						me->left_mouse_y = event->y;

						//cout << "window_button_press: left-mouse press: imgx=" << imgx << " imgy=" << imgy << endl;
					} break;

				case GDK_BUTTON_RELEASE:
					//cout << "window_button_press: left-mouse release" << endl;
					gdk_window_set_cursor(me->window->window, NULL);

 					if( me->left_mouse_pressed && !me->drag_item && 
							(event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK  )
						me->fill_map_tiles();
						
					me->left_mouse_pressed = 0;
					break;

					// zoom in on canvas
				case GDK_2BUTTON_PRESS: case GDK_3BUTTON_PRESS: 
					{
						//printf("window_button_press: double-click\n");
						me->windowToGeo(imgx,imgy);
						me->zoomInGeo(imgx,imgy,1);
					} break;
				default:
					printf("window_button_press: ??\n"); 
			}
			break;
		case RIGHT_MOUSE:
			switch (event->type) {
				// zoom out canvas
				case GDK_2BUTTON_PRESS: case GDK_3BUTTON_PRESS: 
					{
						//printf("window_button_press: double-click\n");
						me->windowToGeo(imgx,imgy);
						me->zoomOutGeo(imgx,imgy,1);
					} break;
				case GDK_BUTTON_PRESS: 
					//printf("window_button_press: right-mouse press\n");
					me->create_control_window(event);
					break;
				case GDK_BUTTON_RELEASE:
					//printf("window_button_press: right-mouse release\n");
					break;
				default:
					printf("window_button_press: ??\n"); 
			}
			break;
		case MIDDLE_MOUSE:
			if( event->type == GDK_BUTTON_PRESS) {
				//printf("window_button_press: middle-mouse\n");
			}
			break;
		default:
			printf("window_button_press: unkown button %i\n", button);

	}

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return FALSE;
}

int GPSMap::window_delete_event_cb(GtkWidget* window, GdkEventAny* e, GPSMap* me)
{
	printf("window_delete_event_cb\n" );
	gtk_main_quit();
	return FALSE;
}

bool GPSMap::item_button_press_event_cb (GooCanvasItem *item, GooCanvasItem *target_item, 
		GdkEventButton *event, GPSMap *me)
{	
	double mouse_x, mouse_y;

	// these are world coordinates
	mouse_x = event->x;
	mouse_y = event->y;

	printf("GPSMap::item_button_press_event_cb: event->type=%i event->x=%f event->y=%f\n", event->type, event->x, event->y);

	switch(event->button) {
		case LEFT_MOUSE:
			break;

		case RIGHT_MOUSE:
			break;

		case MIDDLE_MOUSE:
			break;
		default:
			printf("Unknown mouse button value\n");
			break;
	} 

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return FALSE;
}

bool GPSMap::item_button_release_event_cb (GooCanvasItem  *item, GooCanvasItem  *target_item, 
		GdkEventButton *event, GPSMap *me)
{
	printf("GPSMap::item_button_release_event_cb: event->type=%i event->x=%f event->y=%f\n", event->type, event->x, event->y);
/*
	if( me->drag_item != NULL && me->drag_item == item ) {
		goo_canvas_pointer_ungrab(me->canvas, item, event->time);
		me->drag_item = NULL;
		return TRUE;
	}
*/
	// make sure item is now null, button was released
	return FALSE;
}

bool GPSMap::item_motion_notify_event_cb (GooCanvasItem  *item, GooCanvasItem  *target_item, 
		GdkEventMotion *event, GPSMap *me)
{
	double mouse_x, mouse_y;
	int    m_x, m_y;

	//printf("GPSMap::item_motion_event_cb: event->type=%i event->x=%f event->y=%f\n", event->type, event->x, event->y);

	if( !me ) return false;

	//goo_canvas_grab_focus (me->canvas, item);

	// these are world coordinates
	mouse_x = event->x;
	mouse_y = event->y;

	// if is is_hint, then this is last valid coordinate so use it
	if (event->type == GDK_MOTION_NOTIFY && event->is_hint) {
		// this gets us world coordinates
		gdk_window_get_pointer(event->window, &m_x, &m_y, NULL);
		mouse_x = m_x; 
		mouse_y = m_y;
	} 

	//printf("GPSMap::item_motion_notify_event_cb: event->type=%i event->x=%f event->y=%f mouse_x=%f mouse_y=%f\n",
	//event->type, event->x, event->y, mouse_x, mouse_y);

	if( me->drag_item != NULL && me->drag_item == item ) {
		// Translate the item
		GooCanvasPoints *points;
		g_object_get (G_OBJECT (item), "points", &points, NULL);
		if( !points ) {
			printf("no points\n");
			return FALSE;
		}
		// move closest point to new mouse index
		points->coords[2*me->drag_point_ind] = event->x;
		points->coords[2*me->drag_point_ind+1] = event->y;
		g_object_set(G_OBJECT(item), "points", points, NULL);

		me->item_x = mouse_x;
		me->item_y = mouse_y;

		return TRUE;
	}
	return FALSE;
}

bool GPSMap::item_key_event_cb(GooCanvasItem  *item, GooCanvasItem  *target_item, 
		GdkEventKey *event, GPSMap *map)
{
	printf("GPSMap::item_key_event_cb: item=%p keyval=%i str=%s\n", item, event->keyval, event->string);
	switch( event->keyval ) {
		case 'u':
			goo_canvas_item_raise(item, NULL);
			return TRUE;
			break;
		case 'd':
			goo_canvas_item_lower (item, NULL);
			return TRUE;
			break;
		default:
			break;
	}

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return FALSE;
}

bool GPSMap::set_mouse_info_location(GtkStatusbar *mouse_location)
{
	mouse_status = mouse_location;
	return true;
}

void GPSMap::reset_translation(double dx, double dy)
{
	double tx, ty, scale, rot;
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(movable_root), &tx, &ty, &scale, &rot);
	goo_canvas_item_set_simple_transform(GOO_CANVAS_ITEM(movable_root), dx, dy, scale, rot);

	// update tiles
	fill_map_tiles();
}

bool GPSMap::windowToGeo(double &x, double &y)
{
	double tx, ty, scale, rot;
	//cout << "GPSMap::windowToGeo - x=" << x << " y=" << y << " map_zoom=" << map_zoom << endl;

	// convert from window to root_item
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(movable_root), &tx, &ty, &scale, &rot);
	x = (x - tx) / scale;
	y = (y - ty) / scale;

	x = rad2deg( pixel2lon(map_zoom, x) );
	y = rad2deg( pixel2lat(map_zoom, y) );

	//cout << "GPSMap::windowToGeo - x=" << x << " y=" << y << " map_zoom=" << map_zoom << endl;
	return true;
}

bool GPSMap::geoToLayer(GroupLayers g, double &x, double &y)
{
	double tx, ty, scale, rot;

	x = lon2pixel(map_zoom, deg2rad(x) );
	y = lat2pixel(map_zoom, deg2rad(y) );

	if( g == MAP_LAYER )
		return true;
	else if( g == OSM_LAYER )
		return true;

	// from mapG to movable_root
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(layerG[MAP_LAYER]), &tx, &ty, &scale, &rot);
	x = x * scale + tx;
	y = y * scale + ty;

	// from movable_root to layerg group g
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(layerG[g]), &tx, &ty, &scale, &rot);
	x = x / scale + tx;
	y = y / scale + ty;

	return true;
}

bool GPSMap::layerToGeo(GroupLayers g, double &x, double &y)
{
	double tx, ty, scale, rot;
	// convert from layer to root_item
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(layerG[g]), &tx, &ty, &scale, &rot);
	x = (x - tx) * scale;
	y = (y - ty) * scale;

	// convert from root_item to mapG
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(layerG[MAP_LAYER]), &tx, &ty, &scale, &rot);
	x = (x - tx) / scale;
	y = (y - ty) / scale;

	x = rad2deg( pixel2lon(map_zoom, (x)) );
	y = rad2deg( pixel2lat(map_zoom, (y)) );
	
	return true;
}


bool GPSMap::geoToWindow(double &x, double &y)
{
	double tx, ty, scale, rot;

	x = lon2pixel(map_zoom, deg2rad(x) );
	y = lat2pixel(map_zoom, deg2rad(y) );

	// from OSM to movable_root
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(layerG[MAP_LAYER]), &tx, &ty, &scale, &rot);
	x = x * scale + tx;
	y = y * scale + ty;

	// from movable_root to layerg group g
	goo_canvas_item_get_simple_transform(GOO_CANVAS_ITEM(movable_root), &tx, &ty, &scale, &rot);
	x = x * scale + tx;
	y = y * scale + ty;

	return true;
}

bool GPSMap::zoomFit()
{
	int width, height;
	double new_zoom;

	double dx = 0, dy = 0;
	bool val = true;
	do {
		// my viewable area from my parent widget 
		width = getWidth();
		height = getHeight();
		//cout << "allocation: width=" << width << " height=" << height << endl;

		// total size of all displayed items
		GooCanvasBounds bounds;
		double x_canvas_img_ratio;
		double y_canvas_img_ratio;
		dx = 0; 
		dy = 0;

		goo_canvas_item_get_bounds (GOO_CANVAS_ITEM(zoom_fit_group), &bounds);
		//cout << "bounds: x1=" << bounds.x1 << " y1=" << bounds.y1 << endl
			  //<< "        x2=" << bounds.x2 << " y2=" << bounds.y2 << endl;

		x_canvas_img_ratio = (width)  / (bounds.x2 - bounds.x1);
		y_canvas_img_ratio = (height) / (bounds.y2 - bounds.y1);

		if (x_canvas_img_ratio < y_canvas_img_ratio)
			new_zoom = x_canvas_img_ratio;
		else
			new_zoom = y_canvas_img_ratio;

		// zoom the map layer 
		double deltaZoom = ( log( new_zoom ) / log(2.0) );
		//cout << "GPSMap::zoomFit - new_zoom=" << new_zoom  << " deltaZoom=" << deltaZoom << endl;

		double x = 0, y = 0;
		x = ( bounds.x1 +  bounds.x2 ) / 2.0;
		y = ( bounds.y1 +  bounds.y2 ) / 2.0;

		windowToGeo(x,y);

		// now zoom
		val = adjust_zoom(Round(map_zoom + deltaZoom));

		// get window point of geo coordinates
		geoToWindow( x, y);

		dx = Round(getWidth()  / 2.0 - x);
		dy = Round(getHeight() / 2.0 - y);

		// Translate the entire world
		goo_canvas_item_translate(GOO_CANVAS_ITEM(movable_root), dx, dy);

		// refresh the map
		GdkRectangle rect = {0,0,getWidth(),getHeight()};
		gtk_widget_draw(GTK_WIDGET(canvas), &rect);
	} while( fabs(dx) > 5 && fabs(dy) > 5);

	// update tiles
	fill_map_tiles();

	return val;
}

bool GPSMap::zoomInLevel(int level)
{
	if( level < 0 )
		level = 0;
	else if( level > MAX_ZOOM)
		level = MAX_ZOOM;

	//cout << "GPSMap::zoomInLevel - level=" << level << endl;
	return adjust_zoom(level);
}

bool GPSMap::zoomIn_nofill(bool linear)
{
	double new_zoom = zoom_level;
	if( linear ) {
		if( new_zoom == 0 )
			new_zoom++;
		else if( new_zoom < 0.5 )
			new_zoom = 1.0 / (1.0/new_zoom - 1.0);
		else
			new_zoom++;
	} else {
		new_zoom *= 2.0;
		if( new_zoom == 0 )
			new_zoom = 1;
	}

	if( new_zoom > MAX_ZOOM )
		new_zoom = MAX_ZOOM;

	//cout << "GPSMap::zoomIn - new_zoom=" << new_zoom << endl;
	return adjust_zoom(new_zoom);
}

bool GPSMap::zoomIn(bool linear)
{
	bool val = zoomIn_nofill(linear);

	// update tiles
	fill_map_tiles();

	return val;
}

bool GPSMap::adjust_zoom(double zoom)
{

	// check the requested zoom for a level in our current view
	if( zoom < map_sources[mapSource].min_zoom )
		zoom = map_sources[mapSource].min_zoom;
	else if( zoom > map_sources[mapSource].max_zoom ) 
		zoom = map_sources[mapSource].max_zoom;

	// save and then update zoom level
	int oldZoom = map_zoom;
	zoom_level = Round(zoom);
	map_zoom   = Round(zoom_level);

	//cout << "GPSMap::adjust_zoom - zoom=" << zoom_level << " animate=" << animate_zoom << endl;

	// don't do anything if no zoom change
	if( zoom_level == oldZoom )
		return TRUE;

	//-- set the zoom level for layer items, or translate accordingly --//
	for( int i=0; i < NUM_LAYERS; i++ ) {
		// does the layer exists

		switch( i ) {
			case OSM_LAYER: 
				// don't do any zoom translate on these layers
				break;
			case MAP_LAYER: {
				double new_x, new_y;
				for(int mI = 0; mI < mapCount; mI++) {
					if( geoMap[mI].geoImg ) {
						geoMap[mI].geoImg->getTiePoints(new_x, new_y);

						// convert geographic to base map image coordinates,
						geoToLayer(MAP_LAYER, new_x, new_y);

						// this is the offset for this map
						geoMap[mI].x_off = new_x;
						geoMap[mI].y_off = new_y;

						// get coordinates of new maps tie points
						double w2 = geoMap[mI].geoImg->getWidth() / 2.0;
						double h2 = geoMap[mI].geoImg->getHeight() / 2.0;
						geoMap[mI].geoImg->imageToGeo(w2, h2);

						// determine scale width/height
						geoMap[mI].scale_x = geoMap[mI].geoImg->getDegPerXp() / degPerXp();
						geoMap[mI].scale_y = fabs(geoMap[mI].geoImg->getDegPerYp() / degPerYp(h2));

						// scale and translate image
						double tx, ty, z, rot;
						if( geoMap[mI].img_item ) {
							goo_canvas_item_get_simple_transform( geoMap[mI].img_item, &tx, &ty, &z, &rot);
							goo_canvas_item_set_simple_transform( geoMap[mI].img_item, new_x, new_y, 1.0, rot);
							goo_canvas_item_scale(geoMap[mI].img_item, geoMap[mI].scale_x, geoMap[mI].scale_y );
						}

						

					} else if(  geoMap[mI].geoVideo ) {
						geoMap[mI].geoVideo->getTiePoints(new_x, new_y);

						// convert geographic to base map image coordinates,
						geoToLayer(MAP_LAYER, new_x, new_y);

						// this is the offset for this map
						geoMap[mI].x_off = new_x;
						geoMap[mI].y_off = new_y;

						// get coordinates of new maps tie points
						double w2 = geoMap[mI].geoVideo->getWidth() / 2.0;
						double h2 = geoMap[mI].geoVideo->getHeight() / 2.0;
						geoMap[mI].geoVideo->imageToGeo(w2, h2);

						// determine scale width/height
						geoMap[mI].scale_x = geoMap[mI].geoVideo->getDegPerXp() / degPerXp();
						geoMap[mI].scale_y = fabs(geoMap[mI].geoVideo->getDegPerYp() / degPerYp(h2));

						// scale and translate image
						double tx, ty, z, rot;
						if( geoMap[mI].img_item ) {
							goo_canvas_item_get_simple_transform( geoMap[mI].img_item, &tx, &ty, &z, &rot);
							goo_canvas_item_set_simple_transform( geoMap[mI].img_item, new_x, new_y, 1.0, rot);
							goo_canvas_item_scale(geoMap[mI].img_item, geoMap[mI].scale_x, geoMap[mI].scale_y );
						}


					} 

				}
				break;
			}

			default:
				double zoomFactor = ( zoom_level < oldZoom ? 0.5 : 2.0 );
				if( layer_manager[i] && !layer_zooming[i] )
					layer_manager[i]->translate_from_zoomChange( zoomFactor );
				break;
		}

	}

	// refresh the map
	GdkRectangle rect = {0,0,getWidth(),getHeight()};
	gtk_widget_draw(GTK_WIDGET(canvas), &rect);

	return true;
}

bool GPSMap::zoomInWin(bool linear)
{	
	double x,y;
	x = y = 0;

	// first, get lat lon at window center
	x = getWidth() / 2.0;
	y = getHeight() / 2.0;

	windowToGeo(x,y);

	// now zoom
	zoomIn_nofill(linear);

	// get window point of geo coordinates
	geoToWindow( x, y);

	double dx = Round(getWidth()  / 2.0 - x);
	double dy = Round(getHeight() / 2.0 - y);

	// Translate the entire world
	goo_canvas_item_translate(GOO_CANVAS_ITEM(movable_root), dx, dy);

	// update tiles
	fill_map_tiles();

	return true;
}

bool GPSMap::zoomOutWin(bool linear)
{	
	double x,y;
	x = y = 0;

	// first, get lat lon at window center
	x = getWidth() / 2.0;
	y = getHeight() / 2.0;

	windowToGeo(x,y);

	// now zoom
	zoomOut_nofill(linear);

	// get window point of geo coordinates
	geoToWindow( x, y);

	double dx = Round(getWidth()  / 2.0 - x);
	double dy = Round(getHeight() / 2.0 - y);

	// Translate the entire world
	goo_canvas_item_translate(GOO_CANVAS_ITEM(movable_root), dx, dy);
	// update tiles
	fill_map_tiles();

	return true;
}

bool GPSMap::zoomOutGeo(double x, double y, bool linear)
{	
	// first, get window point of geo coordinates
	double img_x = x;
	double img_y = y;
	geoToWindow( img_x, img_y);

	// now zoom
	zoomOut_nofill(linear);

	// get window point of geo coordinates
	geoToWindow( x, y);

	double dx = Round(img_x - x);
	double dy = Round(img_y - y);

	// if we are not tracking a node, translate map
	NodeIconManager *iconM = (NodeIconManager *)layer_manager[ICON_LAYER];
	if( !iconM || !iconM->isTracking() )
		goo_canvas_item_translate(GOO_CANVAS_ITEM(movable_root), dx, dy);

	// update tiles
	fill_map_tiles();

	return true;
}

void GPSMap::stopNodeTracking()
{	
	// turn off tracking of any nodes
	NodeIconManager *iconM = (NodeIconManager *)layer_manager[ICON_LAYER];
	if( iconM ) iconM->clearAllTrack();
}

void GPSMap::moveToGeo(double lon, double lat, bool animate)
{	
	// get window point of geo coordinates
	double img_x = getMapLon();
	double img_y = getMapLat();
	geoToWindow( img_x, img_y);

	// get window point of geo coordinates
	geoToWindow( lon, lat);

	double dx = Round(img_x - lon);
	double dy = Round(img_y - lat);

	// Translate the entire world
	if(animate && animate_motion) {
		//double x,y,scale,rot;
		//goo_canvas_item_get_simple_transform(child, &x, &y, &scale, &rot);

		goo_canvas_item_animate(GOO_CANVAS_ITEM(movable_root),
				dx, dy,
				1.0, 0.0, FALSE, 
				//ANIMATION_TIME, ANIMATION_TIME / ANIMATION_STEPS,
				1000, 250,
				GOO_CANVAS_ANIMATE_FREEZE);

		animation_count = 0;
		g_timeout_add (200, GPSMap::update_map_animation, this);
		//g_timeout_add (MAP_ANIMATION_TIME / MAP_ANIMATION_STEPS, GPSMap::update_map_animation, this);
		//g_timeout_add (MAP_ANIMATION_TIME, GPSMap::update_map_animation, this);
	} else
		goo_canvas_item_translate(GOO_CANVAS_ITEM(movable_root), dx, dy);

	// update tiles
	fill_map_tiles();
}

gboolean GPSMap::update_map_animation(gpointer data)
{
	if( !data ) 
		return false;
	GPSMap *map = (GPSMap *)data;

	map->fill_map_tiles();
	if( ++(map->animation_count) >= MAP_ANIMATION_STEPS )
		return false;

	return true;
}

bool GPSMap::zoomInGeo(double x, double y, bool linear)
{
	// first, get window point of geo coordinates
	double img_x = x;
	double img_y = y;
	geoToWindow( img_x, img_y);

	// now zoom
	zoomIn_nofill(linear);

	// get window point of geo coordinates
	geoToWindow( x, y);

	double dx = Round(img_x - x);
	double dy = Round(img_y - y);

	// if we are not tracking a node, translate map
	NodeIconManager *iconM = (NodeIconManager *)layer_manager[ICON_LAYER];
	if( !iconM || !iconM->isTracking() )
		goo_canvas_item_translate(GOO_CANVAS_ITEM(movable_root), dx, dy);

	// update tiles
	fill_map_tiles();

	return true;
}

bool GPSMap::zoomOut_nofill(bool linear)
{
	double new_zoom = zoom_level;

	if( linear ) {
		if( new_zoom < 1.0 && new_zoom != 0)
			new_zoom = 1.0 / (1.0/new_zoom + 1.0);
		else
			new_zoom--;
	} else
		new_zoom /= 2.0;

	if( new_zoom < MIN_ZOOM )
		new_zoom = MIN_ZOOM;

	return adjust_zoom(new_zoom);
}


bool GPSMap::zoomOut(bool linear)
{
	bool val = zoomOut_nofill(linear);

	// update tiles
	fill_map_tiles();

	return val;
}

bool GPSMap::zoomReset()
{
	bool val = adjust_zoom(1.0);

	// reset translation of root item back to zero
	reset_translation();

	// update tiles
	fill_map_tiles();

	return val;
}

/*======================================================================
//  Control window.
//----------------------------------------------------------------------*/
int GPSMap::create_control_window(GdkEventButton *event)
{
	// Get the popup menu
	if( !menu )
		menu = gtk_ui_manager_get_widget (menu_manager, "/MapPopup");

	/* Show the menu */
	gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);

	return true;
}


/*======================================================================
//  Map image file diaglog
//----------------------------------------------------------------------*/
bool GPSMap::toggleLayerZooming(GroupLayers g, int setTo)
{
	//-- cannot change zooming of map type layers --//
	if( g == MAP_LAYER || g == RADAR_LAYER || g == RADAR_SIM_LAYER || g == LEVEL2_LAYER )
		return false;

	if( setTo == 0 || setTo == 1 )
		layer_zooming[g] = setTo;	
	else
		layer_zooming[g] ^= 1;	

	// do something

	return layer_zooming[g];
}

int GPSMap::create_wp_cb(GtkToggleAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;

	FPManager *fpM = (FPManager *)me->layer_manager[FP_LAYER];
	if( !fpM )
		return false;

	me->setPlanEditing( gtk_toggle_action_get_active(action) );

	return true;
}

int GPSMap::clear_wp_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;

	FPManager *fpM = (FPManager *)me->layer_manager[FP_LAYER];
	if( !fpM )
		return false;
	fpM->clear_client(MAX_CLIENTS);

/*
	GtkAction *wp_create = gtk_action_group_get_action(me->action_group, "CreateWP");
	if( wp_create ) {
		gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(wp_create), 0);
		me->creating_plan = 0;
	} else cout << "could not get CreateWP action item" << endl;
*/

	return true;
}

int GPSMap::create_dto_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;

	FPManager *fpM = (FPManager *)me->layer_manager[FP_LAYER];
	fpM->create_default_to_plan();

	return true;
}

int GPSMap::create_dlc_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;

	FPManager *fpM = (FPManager *)me->layer_manager[FP_LAYER];
	fpM->create_default_lost_comm_plan();

	return true;
}

int GPSMap::load_wp_cb(GtkAction *action, GPSMap *me)
{
	//printf(" callback_data=%p callback_action=%i widget=%p\n", callback_data, callback_action, widget);
	if( me == NULL )
		return FALSE;

	GtkWidget *dialog;
	char *filename = NULL;

	dialog = gtk_file_chooser_dialog_new ("Open Flight Plan ...",
			NULL,
			GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
			NULL);

	if (me->last_directory != "")
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), me->last_directory.c_str());

	GtkFileFilter *filter = gtk_file_filter_new ();
	gtk_file_filter_add_pattern (filter, "*" FLIGHTPLAN_EXT_STRING);
	gtk_file_chooser_set_filter( GTK_FILE_CHOOSER (dialog), filter);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
	{
		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
	}

	gtk_object_destroy (GTK_OBJECT(filter));
	gtk_widget_destroy (dialog);

	if( filename ) {
		FPManager *fpM = (FPManager *)me->layer_manager[FP_LAYER];
		fpM->add_flightplan(MAX_CLIENTS, filename);
		me->set_last_directory_from_filename(filename);
		g_free (filename);
	}

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return true;
}

int GPSMap::clear_fb_cb(GtkAction *action, GPSMap *me)
{
	//printf("clear_fb_cb\n");
	FBManager *fbM = (FBManager *)me->layer_manager[FB_LAYER];
	if( fbM )
		return fbM->clear_fb_cb(action, fbM);

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return true;
}

int GPSMap::clear_dist_cb(GtkAction *action, GPSMap *me)
{
	//printf("clear_dist_cb\n");
	DMManager *dmM = (DMManager *)me->layer_manager[DM_LAYER];
	if( dmM )
		dmM->clear_client();

	me->setDistEditing( false );

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return true;
}

int GPSMap::load_fb_cb(GtkAction *action, GPSMap *me)
{
	//printf(" callback_data=%p callback_action=%i widget=%p\n", callback_data, callback_action, widget);
	if( me == NULL )
		return FALSE;

	GtkWidget *dialog;
	char *filename = NULL;

	dialog = gtk_file_chooser_dialog_new ("Open Flight Plan ...",
			NULL,
			GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
			NULL);

	if (me->last_directory != "")
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), me->last_directory.c_str());

	GtkFileFilter *filter = gtk_file_filter_new ();
	gtk_file_filter_add_pattern (filter, "*.kml");
	gtk_file_chooser_set_filter( GTK_FILE_CHOOSER (dialog), filter);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
	{
		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
	}

	gtk_object_destroy (GTK_OBJECT(filter));
	gtk_widget_destroy (dialog);

	if( filename ) {
		FBManager *fbM = (FBManager *)me->layer_manager[FB_LAYER];
		fbM->read_from_file(filename);
		me->set_last_directory_from_filename(filename);
		g_free (filename);
	}

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return true;
}

int GPSMap::clear_maps_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;
	me->clear_geoImage();
	return true;
}

int GPSMap::load_image_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;

	//cout << "GPSMap::load_image_cb - me=" << me << " last_dir=" << me->last_directory << endl;

	GtkWidget *dialog;
	char *filename = NULL;

	dialog = gtk_file_chooser_dialog_new ("Open GEO Image ...",
			NULL,
			GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
			NULL);

	if (me->last_directory != "")
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), me->last_directory.c_str());

	GtkFileFilter *filter = gtk_file_filter_new ();
	gtk_file_filter_add_pattern (filter, "*.tiff");
	gtk_file_filter_add_pattern (filter, "*.tif");
	gtk_file_filter_add_pattern (filter, "*.gif");
	gtk_file_filter_add_pattern (filter, "*.jpg");
	gtk_file_filter_add_pattern (filter, "*.jpeg");
	gtk_file_chooser_set_filter( GTK_FILE_CHOOSER (dialog), filter);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
	}

	gtk_object_destroy (GTK_OBJECT(filter));
	gtk_widget_destroy (dialog);

	if( filename ) {
		me->add_geoImage(filename);
		me->set_last_directory_from_filename(filename);
		g_free (filename);
	}

	// Returning FALSE propagates the event to parent items;
	// returning TRUE ends event propagation. 
	return true;
}

void GPSMap::set_last_directory_from_filename(const char *filename) 
{ 
	char *dir_name; 

	dir_name = g_path_get_dirname(filename); 
	if(dir_name) {
		last_directory = dir_name;
		last_directory += "/";
	}
}

bool GPSMap::add_wp(String wpfile)
{
	FPManager *fpM = (FPManager *)layer_manager[FP_LAYER];
	if( !fpM )
		return false;
	return fpM->add_flightplan(MAX_CLIENTS, wpfile);
}

bool GPSMap::save_wp(String wpfile)
{
	FPManager *fpM = (FPManager *)layer_manager[FP_LAYER];
	if( !fpM )
		return false;
	return fpM->save_flightplan(MAX_CLIENTS, -1, wpfile);
}

// create a pop-up error window for use with mesg
void GPSMap::create_error_window(GPSMap *me, const char *mesg)
{
	GtkWidget *dialog;

	dialog = gtk_message_dialog_new (GTK_WINDOW(me->window->window),
			GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_MESSAGE_ERROR,
			GTK_BUTTONS_CLOSE,
			"ERROR: %s",
			mesg);

	g_signal_connect (dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
	gdk_beep();
	gtk_widget_show (dialog);
}

bool GPSMap::toggleLayerShown(GroupLayers g, int setTo)
{	
	if(setTo == 0 || setTo == 1) {
		if( layer_is_shown[g] == setTo )
			return setTo;
		layer_is_shown[g] = setTo;
	} else
		layer_is_shown[g] ^= 1;

	if( layer_manager[g] )
		layer_manager[g]->setShown( layer_is_shown[g] );
	else {
		//cout << "GPSMap::toggleLayerShown -- no manager" << endl;
		if( layer_is_shown[g] ) {
			g_object_set (layerG[g],
					"visibility", GOO_CANVAS_ITEM_VISIBLE,
					NULL);
			if( g == OSM_LAYER ) {
				//cout << "GPSMap::toggleLayerShown - initOSM" << endl;
				initOSM();
			}
		} else {
			g_object_set (layerG[g],
					"visibility", GOO_CANVAS_ITEM_INVISIBLE,
					NULL);

			if( g == OSM_LAYER && osm_map_layer ) {
				goo_canvas_item_remove(  GOO_CANVAS_ITEM( osm_map_layer) );
				osm_map_layer = NULL;
			}
		}
	}

	layer_list.setShown((GooCanvasItem*)g, layer_is_shown[g]); 

	return layer_is_shown[g];
}

int GPSMap::find_closest(GooCanvasPoints *points, double drag_x, double drag_y)
{
	int i=0;
	double xdiff, ydiff, diff, diffMin = INFINITY;
	int minInd = -1;

	for( i=0; i < points->num_points; i++ ) {
		xdiff = points->coords[i*2] - drag_x;
		ydiff = points->coords[i*2+1] - drag_y;
		diff = xdiff*xdiff + ydiff*ydiff;

		if( diff < diffMin ) {
			diffMin = diff;
			minInd = i;
		}
	}

	return minInd;
}

double GPSMap::getMapLat() 
{ 
	double x=0; 
	double y=getHeight()/2.0; 
	windowToGeo(x, y); 
	return y;
}

double GPSMap::getMapLon() 
{ 
	double x=getWidth()/2.0; 
	double y=0; 
	windowToGeo(x, y); 
	return x;
}

// note in UTM, the pixel/meter ratio is a function of latitude
double GPSMap::mPerP(double lat)
{
	if( lat != lat )
		lat = getMapLat();
	double res = (cos(lat * M_PI/180.0) * 2.0*M_PI * 6378137.0) / (double)(TILESIZE << map_zoom);
	return res;
}

double GPSMap::degPerXp()
{
	double dx = rad2deg( pixel2lon( map_zoom, 1 ) - pixel2lon( map_zoom, 0 ) );
	//return fabs(dx);
	return (dx);
}

double GPSMap::degPerYp(double lat)
{
	double x   = 0;
	double h2  = 0;
	double lon = 0;

	if( lat != lat ) lat = getMapLat();

	// get the pixel location of the lat 
	geoToWindow(lon, lat);
	h2 = lat - 1;

	x = 0;
	windowToGeo(x,lat);

	x = 0;
	windowToGeo(x,h2);

	double dy = (lat - h2);

	return (dy);
}

int GPSMap::zoom_in_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;
	me->zoomInWin();
	return TRUE;
}

int GPSMap::zoom_out_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;
	me->zoomOutWin();
	return TRUE;
}

int GPSMap::zoom_fit_cb(GtkAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;
	me->zoomFit();
	return TRUE;
}

int GPSMap::zoom_reset_cb(GtkAction *action, GPSMap *me)
{
	//cout << " action name=" << gtk_action_get_name(action) << endl;
	if( me == NULL )
		return FALSE;
	me->zoomReset();
	return TRUE;
}

int GPSMap::map_dist_cb(GtkToggleAction *action, GPSMap *me)
{
	//cout << " GPSMap::map_dist_cb - me=" << me << endl;
	if( me == NULL )
		return FALSE;

	DMManager *dmM = (DMManager *)me->layer_manager[DM_LAYER];
	if( !dmM )
		return false;

	me->setDistEditing( gtk_toggle_action_get_active(action) );

	return true;
}

int GPSMap::map_search_cb(GtkAction *action, GPSMap *me)
{
	//cout << " action name=" << gtk_action_get_name(action) << endl;
	if( me == NULL )
		return FALSE;

	me->create_search_window();

	return TRUE;
}

int GPSMap::units_cb(GtkRadioAction *action, GtkRadioAction *current, GPSMap *me)
{
	//cout << "GPSMap::osm_mapserver_cb - me=" << me << endl;
	if( !me )
		return FALSE;

	UnitSystem_t unit = (UnitSystem_t) gtk_radio_action_get_current_value(current);
	guiStr.config.units = unit;

	return TRUE;
}

int GPSMap::osm_mapserver_cb(GtkRadioAction *action, GtkRadioAction *current, GPSMap *me)
{
	//cout << "GPSMap::osm_mapserver_cb - me=" << me << endl;
	if( !me )
		return FALSE;

	MapSource source = (MapSource) gtk_radio_action_get_current_value(current);
	me->initOSM(source);

	return TRUE;
}

int GPSMap::toggle_animation(GtkToggleAction *action, GPSMap *me)
{
	if( me == NULL )
		return FALSE;

	if( gtk_toggle_action_get_active(action) ) {
		me->animate_motion = 1;
		//me->animate_zoom = 1;
	} else {
		me->animate_motion = 0;
		//me->animate_zoom = 0;
	}

	return TRUE;
}

void GPSMap::convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel,
		gchar *buffer, const gchar initial,
		const gchar *const quadrant)
{
	gchar *ptr = buffer;
	gint n;

	if (initial)
		*ptr++ = initial;

	for(n = zoomlevel-1; n >= 0; n--)
	{
		gint xbit = (x >> n) & 1;
		gint ybit = (y >> n) & 1;
		*ptr++ = quadrant[xbit + 2 * ybit];
	}

	*ptr++ = '\0';
}

void GPSMap::inspect_map_uri()
{
	uri_format = 0;
	if (g_strrstr(repo_uri, URI_MARKER_X))
		uri_format |= URI_HAS_X;

	if (g_strrstr(repo_uri, URI_MARKER_Y))
		uri_format |= URI_HAS_Y;

	if (g_strrstr(repo_uri, URI_MARKER_Z))
		uri_format |= URI_HAS_Z;

	if (g_strrstr(repo_uri, URI_MARKER_S))
		uri_format |= URI_HAS_S;

	if (g_strrstr(repo_uri, URI_MARKER_Q))
		uri_format |= URI_HAS_Q;

	if (g_strrstr(repo_uri, URI_MARKER_Q0))
		uri_format |= URI_HAS_Q0;

	if (g_strrstr(repo_uri, URI_MARKER_YS))
		uri_format |= URI_HAS_YS;

	if (g_strrstr(repo_uri, URI_MARKER_R))
		uri_format |= URI_HAS_R;

	if (g_strrstr(repo_uri, URI_MARKER_W))
		uri_format |= URI_HAS_W;

	if (g_strrstr(repo_uri, URI_MARKER_H))
		uri_format |= URI_HAS_H;

	if (g_strrstr(repo_uri, URI_MARKER_LAT))
		uri_format |= URI_HAS_LAT;

	if (g_strrstr(repo_uri, URI_MARKER_LON))
		uri_format |= URI_HAS_LON; 

	if (g_strrstr(repo_uri, "google.com")) {
			the_google = true;
	} else 
		the_google = false;

	
	//g_debug("URI Format: 0x%X (google: %X)", uri_format, the_google);
}

gchar * GPSMap::replace_map_uri(const gchar *uri, int zoom, int x, int y, int source)
{        
	char *url;
	unsigned int i;
	char location[22];

	i = 1;
	url = g_strdup(uri);
	while (i < URI_FLAG_END)
	{
		char *s;
		char *old;

		old = url;
		switch(i & uri_format)
		{
			case URI_HAS_X:
				s = g_strdup_printf("%d", x);
				url = replace_string(url, URI_MARKER_X, s);
				////g_debug("FOUND " URI_MARKER_X);
				break;
			case URI_HAS_Y:
				s = g_strdup_printf("%d", y);
				url = replace_string(url, URI_MARKER_Y, s);
				////g_debug("FOUND " URI_MARKER_Y);
				break;
			case URI_HAS_Z:
				s = g_strdup_printf("%d", zoom);
				url = replace_string(url, URI_MARKER_Z, s);
				////g_debug("FOUND " URI_MARKER_Z);
				break;
			case URI_HAS_S:
				s = g_strdup_printf("%d", map_sources[source].max_zoom - zoom);
				url = replace_string(url, URI_MARKER_S, s);
				////g_debug("FOUND " URI_MARKER_S);
				break;
			case URI_HAS_Q:
				convert_coords_to_quadtree_string(x,y,zoom,location,'t',"qrts");
				s = g_strdup_printf("%s", location);
				url = replace_string(url, URI_MARKER_Q, s);
				////g_debug("FOUND " URI_MARKER_Q);
				break;
			case URI_HAS_Q0:
				convert_coords_to_quadtree_string(x,y,zoom,location,'\0', "0123");
				s = g_strdup_printf("%s", location);
				url = replace_string(url, URI_MARKER_Q0, s);
				////g_debug("FOUND " URI_MARKER_Q0);
				break;
			case URI_HAS_YS:
				//          s = g_strdup_printf("%d", y);
				//          url = replace_string(url, URI_MARKER_YS, s);
				g_warning("FOUND " URI_MARKER_YS " NOT IMPLEMENTED");
				//            retval = g_strdup_printf(repo->url,
				//                    tilex,
				//                    (1 << (MAX_ZOOM - zoom)) - tiley - 1,
				//                    zoom - (MAX_ZOOM - 17));
				break;
			case URI_HAS_R:
				s = g_strdup_printf("%d", g_random_int_range(0,4));
				url = replace_string(url, URI_MARKER_R, s);
				////g_debug("FOUND " URI_MARKER_R);
				break;
			case URI_HAS_W:
				s = g_strdup_printf("%d", getWidth());
				url = replace_string(url, URI_MARKER_W, s);
				//cout << " width=" << getWidth() << " s=" << s << " url=" << url << endl;
				////g_debug("FOUND " URI_MARKER_W);
				break;
			case URI_HAS_H:
				s = g_strdup_printf("%d", getHeight());
				url = replace_string(url, URI_MARKER_H, s);
				////g_debug("FOUND " URI_MARKER_H);
				break;
			case URI_HAS_LAT:
				s = g_strdup_printf("%.7f", getMapLat());
				url = replace_string(url, URI_MARKER_LAT, s);
				////g_debug("FOUND " URI_MARKER_LAT);
				break;
			case URI_HAS_LON:
				s = g_strdup_printf("%.7f", getMapLon());
				url = replace_string(url, URI_MARKER_LON, s);
				////g_debug("FOUND " URI_MARKER_LON);
				break;
			default:
				s = NULL;
				break;
		}

		if (s) {
			g_free(s);
			g_free(old);
		}

		i = (i << 1);

	}

	return url;
}

void GPSMap::fill_map_tiles()
{
	int i,j, width, height, tile_x0, tile_y0, tiles_nx, tiles_ny;
	int offset_xn = 0;
	int offset_yn = 0;
	int offset_x = 0;
	int offset_y = 0;

	// get coordinate of NW corner
	int x0;
	int y0;
	
	double x,y;

	// adjust the scale with the new image view
	setup_scale();

	if( !layer_is_shown[GPSMap::OSM_LAYER] ) return;
	//cout << "filling image with tiles" << endl;

	// get the top-left lat/lon coordinates
	x = y = 0;
	windowToGeo(x,y);

	// convert the lat/lon coordinates to mercator tile pixel
	x0 = (int)Round(lon2pixel( map_zoom, deg2rad(x) ));
	y0 = (int)Round(lat2pixel( map_zoom, deg2rad(y) ));

	// check bounds
	x0 = (x0 < 0 ? 0 : x0);	
	y0 = (y0 < 0 ? 0 : y0);	

	//printf("Fill tiles: %d,%d z:%d\n", x0, y0, map_zoom);

	// figure out how much the window is oversized
	offset_x = x0 % TILESIZE;
	offset_y = y0 % TILESIZE;

	width  = getWidth();
	height = getHeight();

	//cout << "\twidth=" << width << " height=" << height << endl;
	//cout << "\toffset_x=" << offset_x << " offset_y=" << offset_y << endl;

	// calculate number of tiles required for display
	tiles_nx = (width + offset_x) / TILESIZE + 1;
	tiles_ny = (height + offset_y) / TILESIZE + 1;

	// calculate top-left tile 
	tile_x0 =  (x0 / TILESIZE);
	tile_y0 =  (y0 / TILESIZE);

	//cout << "\ttile_x0=" << tile_x0 << " tile_y0=" << tile_y0 << endl;

	// calculate top-left tile coordinates
	offset_x = tile_x0 * TILESIZE;
	offset_y = tile_y0 * TILESIZE; 
	offset_xn = offset_x;
	offset_yn = offset_y;
	
	// delete any previously loaded layer, and create new one
	if( osm_map_layer )
		goo_canvas_item_remove(  GOO_CANVAS_ITEM( osm_map_layer) );
	osm_map_layer = GOO_CANVAS_GROUP( goo_canvas_group_new (GOO_CANVAS_ITEM(layerG[OSM_LAYER]), NULL)) ;

	switch( mapSource ) {
		case GOOGLE_DYNAMIC:
		case GOOGLE_DYNAMIC_SAT:
		case GOOGLE_DYNAMIC_TERRAIN:
		case GOOGLE_DYNAMIC_HYBRID:
		case GOOGLE_DYNAMIC_MOBILE:
			load_tile(map_zoom, x0, x0, x0, y0);
			break;

		default:
			// loop through all required tiles and load (or download) the tile image
			for (i=tile_x0; i<(tile_x0+tiles_nx);i++) {
				for (j=tile_y0;  j<(tile_y0+tiles_ny); j++) {
					// It does not make sense to have the map wrap around 
					if( !(j<0 || i<0 || i>=exp(map_zoom * M_LN2) ) )
					{
						load_tile(map_zoom, i, j, offset_xn, offset_yn);
					}
					offset_yn += TILESIZE;
				}
				offset_xn += TILESIZE;
				offset_yn = offset_y;
			}
			break;
	}
}

void GPSMap::blit_tile( GdkPixbuf *pixbuf, int offset_x, int offset_y)
{     
	//g_debug("==> Drawing tile @ %d,%d (w:%d h:%d)", offset_x,offset_y, 
	            //gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf));

	double x = offset_x;
	double y = offset_y;

	// see if we exist 
	if( !osm_map_layer )
		return;

	// goocanvas will now have its own copy of the pixbuf
	GooCanvasItem * item;
	item = goo_canvas_image_new( GOO_CANVAS_ITEM(osm_map_layer), pixbuf, x , y , NULL);

	// refresh the map
	gtk_widget_queue_draw(GTK_WIDGET(canvas));
}

void GPSMap::tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
{
	int fd;
	tile_download_t *dl = (tile_download_t *)user_data;
	GPSMap *map = (GPSMap *)(dl->map);

	//cout << "GPSMap::tile_download_complete : " << dl->filename << endl;
	if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
	{
		if (g_mkdir_with_parents(dl->folder,0700) == 0)
		{
			fd = open(dl->filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
			if (fd != -1)
			{
				write (fd, msg->response_body->data, msg->response_body->length);
				//g_debug("Wrote %lld bytes to %s", msg->response_body->length, dl->filename);
				close (fd);

/*
				char *colorStr = NULL;
				// do some image processing to make transparent background
				switch( map->mapSource ) {
					case OSM_MAPNIK:
						colorStr = "#f1eee8";
						break;
					case OSM_RENDERER:
						colorStr = "#f8f8f8";
						break;
					case OSM_CYCLE:
						colorStr = "#ffffff";
						break;
					default:
						break;
				}

				if( colorStr ) {
					char convertStr[256];
					sprintf(convertStr, "convert \"%s\" -fuzz 50 -transparent \"%s\" \"%s\"", dl->filename, colorStr, dl->filename);
					int ret = system( convertStr );
					if( ret < 0 )
						perror("system command");
				}
*/

				/* Redraw the area of the screen */
				if ( msg->response_body->length > 0 )
				{
					/* Unless these tiles were explicityly just queued, and 
						should not be redrawn */
					if (dl->offset_x != G_MININT && dl->offset_y != G_MININT)
					{
						GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (dl->filename, NULL);
						if(pixbuf)
						{
							//g_debug("Found tile %s", dl->filename);
							map->blit_tile(pixbuf, dl->offset_x, dl->offset_y);
							g_object_unref (pixbuf);
						} else
							perror("could nod load pixbuf file");
					}
				}

				//if( !map->map_auto_cache ) {
					//if( remove( dl->filename ) != 0 )
						//perror( "Error deleting file" );
				//}
			}
		}
		else
		{
			g_warning("Error creating tile download directory: %s", dl->folder);
		}

		g_hash_table_remove(map->tile_queue, dl->uri);

		g_free(dl->uri);
		g_free(dl->folder);
		g_free(dl->filename);
		g_free(dl);
	}
	else
	{
		//printf("Error downloading tile: %d - %s", msg->status_code, msg->reason_phrase);
		if (msg->status_code == SOUP_STATUS_NOT_FOUND)
		{
			//printf("\n\t --> %s\n", dl->uri);
			g_hash_table_insert(map->missing_tiles, dl->uri, NULL);
			g_hash_table_remove(map->tile_queue, dl->uri);
		}
		else //if (msg->status_code == SOUP_STATUS_CANCELLED)
		{
			g_hash_table_remove(map->tile_queue, dl->uri);
			//printf("\n\t --> CANCELLED\n");
		}
		//else
		//{
			//printf("\n\t --> REQUEUEING\n");
			//soup_session_requeue_message(session, msg);
		//}
	}

	map->numDownloading = g_hash_table_size(map->tile_queue);
	//cout << "Remaining downloads: " << map->numDownloading << endl;

	// see if we are done downloading all tiles
	if( map->numDownloading == 0 ) {
		if( map->busy_cursor) {
			gdk_window_set_cursor(GTK_WIDGET(map->window)->window, NULL); 
			map->busy_cursor = NULL;
			gdk_flush();
		}
	}
}

void GPSMap::download_tile (int zoom, int x, int y, int offset_x, int offset_y)
{
	SoupMessage *msg;
	tile_download_t *dl = g_new0(tile_download_t,1);

	// check zoom bounds
	if( zoom < map_sources[mapSource].min_zoom || zoom > map_sources[mapSource].max_zoom ) {
		printf("Invalid zoom level for current OSM source\n");
		return;
	}

	// calculate the uri to download  
	dl->uri = replace_map_uri(repo_uri, zoom, x, y, mapSource);

	//printf("\tTile: x=%d, y=%d, z=%d -- offset x=%d, y=%d", x, y, zoom, offset_x, offset_y);

	//check the tile has not already been queued for download, 
	//or has been attempted, and its missing
	if (  g_hash_table_lookup_extended(tile_queue, dl->uri, NULL, NULL) ||
			g_hash_table_lookup_extended(missing_tiles, dl->uri, NULL, NULL) )
	{
		//printf("already downloading (or missing)\n");
		g_free(dl->uri);
		g_free(dl);
	} else {
		dl->folder = g_strdup_printf("%s/%d/%d/",cache_dir, zoom, x);
		dl->filename = g_strdup_printf("%s/%d/%d/%d.%s",cache_dir, zoom, x, y, map_sources[mapSource].fig_type);
		dl->map = this;
		dl->offset_x = offset_x;
		dl->offset_y = offset_y;


		if( mapSource == GOOGLE_DYNAMIC
		 || mapSource == GOOGLE_DYNAMIC_SAT
		 || mapSource == GOOGLE_DYNAMIC_TERRAIN
		 || mapSource == GOOGLE_DYNAMIC_HYBRID
		 || mapSource == GOOGLE_DYNAMIC_MOBILE )
		soup_session_abort(soup_session);

		msg = soup_message_new (SOUP_METHOD_GET, dl->uri);
		if (msg) {
			if (the_google) {
				//Set maps.google.com as the referrer
				//g_debug("Setting Google Referrer");
				soup_message_headers_append(msg->request_headers, "Referer", "http://maps.google.com/");
				//For google satelite also set the appropriate cookie value
				if (uri_format & URI_HAS_Q) {
					const char *cookie = g_getenv("GOOGLE_COOKIE");
					if (cookie) {
						//g_debug("Adding Google Cookie");
						soup_message_headers_append(msg->request_headers, "Cookie", cookie);
					}
				}
			}

			g_hash_table_insert (tile_queue, dl->uri, msg);
			numDownloading = g_hash_table_size(tile_queue);
			soup_session_queue_message (soup_session, msg, GPSMap::tile_download_complete, dl);

			//printf("queued download (queue size=%i)\n", numDownloading);
			//printf("\t%s\n", dl->uri);
			
			// change the cursor
			busy_cursor = gdk_cursor_new(GDK_WATCH); 
			gdk_window_set_cursor(GTK_WIDGET(window)->window, busy_cursor); 
			gdk_cursor_unref(busy_cursor);
			gdk_flush();

		} else {
			g_warning("Could not create soup message");
			g_free(dl->uri);
			g_free(dl->folder);
			g_free(dl->filename);
			g_free(dl);
		}
	}
}

void GPSMap::load_tile(int zoom, int x, int y, int offset_x, int offset_y)
{
	gchar *filename;
	GdkPixbuf *pixbuf;

	//printf("Load tile %d,%d (%d,%d) z:%d\n", x, y, offset_x, offset_y, zoom);
	
	filename = g_strdup_printf("%s/%u/%u/%u.%s", cache_dir, zoom, x, y, map_sources[mapSource].fig_type);

	pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
	if(pixbuf)
	{
		//g_debug("Found tile %s", filename);
		blit_tile(pixbuf, offset_x, offset_y);
		g_object_unref (pixbuf);
	}
	else
	{
		if (map_auto_download)
			download_tile(zoom,x,y,offset_x,offset_y);
	}
	g_free(filename);
}

typedef struct {
	int zoom;
	double dist_m;
	double dist_ft;
	const char *metric;
	const char *standard;
} ZoomLevelTable;

ZoomLevelTable zoomTable[21] = {
 {  0, INFINITY,   INFINITY, "",""},
 {  1, 5e6,   5000*MILES_TO_FEET, "5000 km","5000 mi"},
 {  2, 2e6,   2000*MILES_TO_FEET, "2000 km","2000 mi"},
 {  3, 2e6,   1000*MILES_TO_FEET, "2000 km","1000 mi"},
 {  4, 1e6,   500*MILES_TO_FEET, "1000 km","500 mi"},
 {  5, 200e3, 200*MILES_TO_FEET, "200 km","200 mi"},
 {  6, 200e3, 100*MILES_TO_FEET, "200 km","100 mi"},
 {  7, 100e3, 50*MILES_TO_FEET, "100 km","50 mi"},
 {  8, 50e3,  20*MILES_TO_FEET, "50 km","20 mi"},
 {  9, 20e3,  10*MILES_TO_FEET, "20 km","10 mi"},
 { 10, 10e3,  5*MILES_TO_FEET, "10 km","5 mi"},
 { 11, 5e3,   2*MILES_TO_FEET,"5 km","2 mi"},
 { 12, 2e3,   2*MILES_TO_FEET,"2 km","2 mi"},
 { 13, 1e3,   1*MILES_TO_FEET,"1 km","1 mi"},
 { 14, 500,   2000, "500 m","2000 ft"},
 { 15, 200,   1000,"200 m","1000 ft"},
 { 16, 200,   500, "200 m","500 ft"},
 { 17, 100,   200, "100 m","200 ft"},
 { 18, 50,    100, "50 m","100 ft"},
 { 19, 20,    50, "20 m","50 ft"},
 { 20, 20,    20, "20 m","20 ft"}
};

void GPSMap::setup_scale()
{
	GooCanvasItem *item;
	double x0 = 10; 
	double y0 = getHeight() - 25;
	double h  = 10;
	double w, w_m, w_e;

	// calculate the line length at this zoom
	w_m = zoomTable[map_zoom].dist_m / mPerP(); 
	w_e = zoomTable[map_zoom].dist_ft * 0.3048 / mPerP(); 
	w = (w_m > w_e ? w_m : w_e);

	if( scale_item )
		goo_canvas_item_remove(scale_item);
	scale_item = GOO_CANVAS_ITEM(goo_canvas_group_new (root_item, NULL));

	// draw the base leg
	item = goo_canvas_polyline_new_line( scale_item,
			x0, y0 - h,
			x0, y0 + h,
			"line-width", 2.0,
			"stroke_color", "black",
			NULL);

	// draw the metric leg
	item = goo_canvas_polyline_new_line( scale_item,
			x0 + w_m, y0,
			x0 + w_m, y0+h,
			"line-width", 2.0,
			"stroke_color", "black",
			NULL);
	item = goo_canvas_text_new (scale_item, zoomTable[map_zoom].metric,
	      x0+10, y0+2, -1,
			GTK_ANCHOR_NW,
			"font", "Sans 8px",
			"fill-color", "black",
			NULL);

	// draw the english leg
	item = goo_canvas_polyline_new_line( scale_item,
			x0 + w_e, y0,
			x0 + w_e, y0-h,
			"line-width", 2.0,
			"stroke_color", "black",
			NULL);
	item = goo_canvas_text_new (scale_item, zoomTable[map_zoom].standard,
	      x0+10, y0-10, -1,
			GTK_ANCHOR_NW,
			"font", "Sans 8px",
			"fill-color", "black",
			NULL);

		
	// draw the horizontal component
	item = goo_canvas_polyline_new_line( scale_item,
			x0, y0,
			x0 + w, y0,
			"line-width", 2.0,
			"stroke_color", "black",
			NULL);
}

void GPSMap::setDistEditing( bool val )
{
	//cout << "GPSMap::setDistEditing - val=" << val << endl;
	creating_dist = val;

	DMManager *dmM = (DMManager *)layer_manager[DM_LAYER];
	if( !dmM ) return;
	dmM->setPlanEditing(creating_dist);

	if( val ) dmM->clear_client();

	GtkAction *wp_create = gtk_action_group_get_action(action_group, "DistanceMeasure");
	if( wp_create && val == FALSE ) {
		gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(wp_create), 0);
	}
}

void GPSMap::setPlanEditing( bool val )
{
	creating_plan = val;

	FPManager *fpM = (FPManager *)layer_manager[FP_LAYER];
	if( !fpM ) return;
	fpM->setPlanEditing(creating_plan);

	if( val ) fpM->clear_client(MAX_CLIENTS);

	GtkAction *wp_create = gtk_action_group_get_action(action_group, "CreateWP");
	if( wp_create && val == FALSE ) {
		gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(wp_create), 0);
	} 
}

void GPSMap::create_search_window ()
{
	//cout << "GPSMap::create_search_window()" << endl;
	if( guiStr.builder == NULL ) {
		cout << "create_search_window() - no builder for gui" << endl;
		return;
	}

	// create window and setup signals
	if( !search_window ) {
		search_window = GTK_WIDGET (gtk_builder_get_object (guiStr.builder, "geocode_dialog"));
		search_entry = GTK_ENTRY (gtk_builder_get_object (guiStr.builder, "search_entry"));

		if( !search_window || !search_entry ) {
			cout << "Invalid xml ui file for search dialog" << endl;
			return;
		}

		// add the dialog response buttons
		apply_button  = GTK_WIDGET (gtk_builder_get_object (guiStr.builder, "search_ok"));
		cancel_button = GTK_WIDGET (gtk_builder_get_object (guiStr.builder, "search_cancel"));

		// callbacks / signals
		g_signal_connect (GTK_OBJECT(search_window), "delete_event", 
				G_CALLBACK(GPSMap::search_window_destroy), search_window);
		g_signal_connect (GTK_OBJECT(apply_button), "clicked", 
				G_CALLBACK(GPSMap::search_button_clicked), this);
		g_signal_connect (G_OBJECT(cancel_button), "clicked", 
				G_CALLBACK(GPSMap::search_button_clicked), this);

		g_object_set_data(G_OBJECT(apply_button), "label", (gpointer)"apply");
		g_object_set_data(G_OBJECT(cancel_button), "label", (gpointer)"cancel");
	}

	gtk_widget_show_all (search_window);
}

// If you return FALSE in the "delete_event" signal handler, GTK will emit
// the "destroy" signal. Returning TRUE means you don't want the window to be
// destroyed.  
gboolean GPSMap::search_window_destroy(GtkWidget * widget, GdkEventKey * event, gpointer user_data)
{
	if( user_data ) {
		gtk_widget_hide_all (GTK_WIDGET(user_data));
		return TRUE;
	}
	return FALSE;
}

void GPSMap::search_button_clicked(GtkButton *button, gpointer user_data)
{
	char * label = (char *)g_object_get_data(G_OBJECT(button), "label");
	//printf( " -- GPSMap::search_button_clicked : %s -- \n", label);

	GPSMap *me = (GPSMap *)user_data;
	if( !me ) return;

	// hide the window, it is not destroyed
	gtk_widget_hide(me->search_window);

	// if not the apply button, need to cancel search
	if( strcmp(label,"cancel") == 0 ) {
		return;
	}

	// get the search string
	const char *search = gtk_entry_get_text(me->search_entry);
	me->geocoder.search(search);
}
